wohoo blocks working

This commit is contained in:
lingdocs 2022-06-20 17:45:40 -05:00
parent 560387d969
commit 96b91e6336
28 changed files with 1307 additions and 2635 deletions

View File

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

View File

@ -1,8 +1,8 @@
import { getShort } from "../../lib/p-text-helpers";
import * as T from "../../types";
import Examples from "../Examples";
import { getShort } from "../lib/p-text-helpers";
import * as T from "../types";
import Examples from "./Examples";
function EPTextDisplay({ compiled, opts, justify, onlyOne }: {
function CompiledPTextDisplay({ compiled, opts, justify, onlyOne }: {
compiled: {
ps: T.SingleOrLengthOpts<T.PsString[]>;
e?: string[] | undefined;
@ -30,4 +30,4 @@ function EPTextDisplay({ compiled, opts, justify, onlyOne }: {
</div>;
}
export default EPTextDisplay;
export default CompiledPTextDisplay;

View File

@ -1,405 +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.
*
*/
import { useEffect, useReducer } from "react";
import VerbInfo from "./verb-info/VerbInfo";
import VerbFormDisplay from "./VerbFormDisplay";
import ButtonSelect from "./ButtonSelect";
import Hider from "./Hider";
import { getForms } from "../lib/conjugation-forms";
import { conjugateVerb } from "../lib/verb-conjugation";
import PersonSelection from "./PersonSelection";
import {
incrementPerson,
parseEc,
} from "../lib/misc-helpers";
import * as T from "../types";
import {
randomPerson,
isInvalidSubjObjCombo,
} from "../lib/np-tools";
const VerbChoiceWarning = () => (
<>
<div className="alert alert-warning d-block mx-auto mt-3" role="alert" style={{ maxWidth: "500px" }}>
<i className="fas fa-exclamation-triangle" /> This verb can be used in different ways!
</div>
<p>Choose which way to use it:</p>
</>
);
const stateLocalStorageName = "explorerState6";
// remember to increment the stateLocalStorageName whenever changing
// the State type
type State = {
mode: "chart" | "sentence";
subject: T.Person,
object: T.Person,
negative: boolean,
compoundTypeSelected: "stative" | "dynamic";
transitivitySelected: "transitive" | "grammatically transitive";
compoundComplementVersionSelected: "sing" | "plur";
showingStemsAndRoots: boolean;
formsOpened: string[];
showingFormInfo: boolean;
}
type Action = {
type: "choose compound type",
payload: "stative" | "dynamic",
} | {
type: "set forms opened",
payload: string,
} | {
type: "set compound complement version",
payload: "sing" | "plur",
} | {
type: "toggle showingStemsAndRoots",
} | {
type: "setState",
payload: State,
} | {
type: "setMode",
payload: "chart" | "sentence",
} | {
type: "setPerson",
payload: { setting: "subject" | "object", person: T.Person },
} | {
type: "randomPerson",
payload: "subject" | "object",
} | {
type: "setShowingFormInfo",
payload: boolean,
} | {
type: "setTransitivitySelected",
payload: "transitive" | "grammatically transitive",
} | {
type: "setNegative",
payload: boolean,
};
function oppositeRole(x: "subject" | "object"): "subject" | "object" {
return x === "subject" ? "object" : "subject";
}
function reducer(state: State, action: Action): State {
function applyFormOpen(
payload: string,
formsOpened: string[],
): string[] {
if (formsOpened.includes(payload)) {
return formsOpened.filter((f) => f !== payload);
}
return [...formsOpened, payload];
}
function setPerson({ setting, person }: { setting: "subject" | "object", person: T.Person }): State {
let newPerson = person;
let otherPerson = state[oppositeRole(setting)];
let otherSetting = oppositeRole(setting);
while (isInvalidSubjObjCombo(newPerson, otherPerson)) {
otherPerson = incrementPerson(otherPerson);
}
return { ...state, [setting]: newPerson, [otherSetting]: otherPerson };
}
switch(action.type) {
case "choose compound type":
return { ...state, compoundTypeSelected: action.payload };
case "set forms opened":
return {
...state,
formsOpened: applyFormOpen(action.payload, state.formsOpened),
};
case "set compound complement version":
return { ...state, compoundComplementVersionSelected: action.payload };
case "toggle showingStemsAndRoots":
return { ...state, showingStemsAndRoots: !state.showingStemsAndRoots };
case "setState":
return action.payload;
case "setMode":
return { ...state, mode: action.payload };
case "setPerson":
return setPerson(action.payload);
case "randomPerson":
return {
...state,
[action.payload]: randomPerson({
prev: state[action.payload === "subject" ? "object" : "subject"]
}),
};
case "setShowingFormInfo":
return {
...state,
showingFormInfo: action.payload,
};
case "setTransitivitySelected":
return {
...state,
transitivitySelected: action.payload,
};
case "setNegative":
return {
...state,
negative: action.payload,
};
default:
throw new Error();
}
}
const initialState: State = {
subject: 0,
object: 2,
negative: false,
compoundTypeSelected: "stative",
transitivitySelected: "transitive",
mode: "chart",
compoundComplementVersionSelected: "plur",
showingStemsAndRoots: false,
showingFormInfo: false,
formsOpened: [],
};
function ConjugationViewer({ entry, complement, textOptions, showOnly, highlightInRootsAndStems, hidePastParticiple, sentenceLevel }: {
entry: T.DictionaryEntry,
complement?: T.DictionaryEntry,
textOptions: T.TextOptions,
showOnly?: string | string[],
highlightInRootsAndStems?: T.RootsOrStemsToHighlight,
hidePastParticiple?: boolean,
sentenceLevel?: "easy" | "medium" | "hard",
}) {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const stateRaw = localStorage.getItem(stateLocalStorageName);
if (stateRaw) {
try {
const payload = JSON.parse(stateRaw) as State;
dispatch({ type: "setState", payload });
} catch (e) {
console.error("error parsing saved state JSON", e);
}
}
}, []);
useEffect(() => {
localStorage.setItem(stateLocalStorageName, JSON.stringify(state));
});
const conjugation = (() => {
if (!(entry.c && entry.c.slice(0, 2) === "v.")) return undefined;
try {
return conjugateVerb(entry, complement);
} catch(e) {
console.error("conjugation error", e);
return undefined;
}
})();
if (conjugation === undefined) {
// don't show the conjugation viewer if the verb can't be conjugated
return null;
}
const verbConj1 = ("dynamic" in conjugation)
? conjugation[state.compoundTypeSelected]
: ("transitive" in conjugation)
? conjugation[state.transitivitySelected === "transitive" ? "transitive" : "grammaticallyTransitive"]
: conjugation;
const verbConj = (verbConj1.singularForm && state.compoundComplementVersionSelected === "sing")
? verbConj1.singularForm
: verbConj1;
const englishConjugation: T.EnglishVerbConjugation | undefined = entry.ec ? {
ec: parseEc(entry.ec),
ep: entry.ep,
} : undefined;
const limitTo = !showOnly
? undefined
: Array.isArray(showOnly)
? showOnly
: [showOnly];
const forms = getForms({
conj: verbConj,
filterFunc: [
...limitTo ? [(f: T.DisplayForm): boolean => limitTo.includes(f.label)
] : [],
],
mode: state.mode,
subject: state.subject,
object: state.object,
negative: state.negative,
sentenceLevel,
englishConjugation,
});
const sentencesAvailable = forms.some((form) => "sentence" in form);
return <div className="mb-4">
{"transitive" in conjugation && <div className="text-center my-2">
<VerbChoiceWarning />
<div>
<ButtonSelect
options={[
{
label: "Transitive", value: "transitive" },
{
label: "Grammatically Transitive",
value: "grammatically transitive",
},
]}
value={state.transitivitySelected}
handleChange={(p) => dispatch({ type: "setTransitivitySelected", payload: p })}
/>
</div>
</div>}
{"dynamic" in conjugation && <div className="text-center my-2">
<VerbChoiceWarning />
<div>
<ButtonSelect
options={[
{ label: "Dynamic", value: "dynamic" },
{
label: `${conjugation.info.type === "dynamic or generative stative compound" ? "Generative " : ""}Stative`,
value: "stative",
},
]}
value={state.compoundTypeSelected}
handleChange={(p) => dispatch({ type: "choose compound type", payload: p })}
/>
</div>
</div>}
{verbConj1.singularForm && <div className="text-center my-2">
<VerbChoiceWarning />
<div>
<ButtonSelect
small
options={[
// @ts-ignore - TODO - make this a bit safer
{ label: `Sing. ${verbConj1.info.objComplement.entry.p}`, value: "sing" },
// @ts-ignore - TODO - make this a bit safer
{ label: `Plur. ${verbConj1.info.objComplement.plural.p}`, value: "plur" },
]}
value={state.compoundComplementVersionSelected}
handleChange={(p) => dispatch({ type: "set compound complement version", payload: p })}
/>
</div>
</div>}
<VerbInfo
info={verbConj.info}
textOptions={textOptions}
showingStemsAndRoots={state.showingStemsAndRoots}
highlightInRootsAndStems={highlightInRootsAndStems}
toggleShowingSar={() => dispatch({ type: "toggle showingStemsAndRoots" })}
hidePastParticiple={hidePastParticiple}
hideTypeInfo={!!limitTo}
/>
<div className="d-flex flex-row align-items-center justify-content-around flex-wrap mt-4 mb-2">
{sentencesAvailable && <div className="mb-3">
<ButtonSelect
options={[
{ label: `Chart${forms.length !== 1 ? "s" : ""}`, value: "chart" },
{ label: `Sentences`, value: "sentence" },
]}
value={state.mode}
handleChange={(p) => dispatch({ type: "setMode", payload: p })}
/>
</div>}
{!limitTo && <>
<div className="form-group form-check">
<input
type="checkbox"
className="form-check-input"
checked={state.showingFormInfo}
onChange={(e) => {
dispatch({ type: "setShowingFormInfo", payload: e.target.checked })
}}
/>
<label className="form-check-label">Show Form Info</label>
</div>
</>}
</div>
{(state.mode === "sentence" && sentencesAvailable) &&
<div className="position-sticky pb-1" style={{ top: 0, background: "var(--theme-shade)", zIndex: 1000 }}>
<PersonSelection
subject={state.subject}
object={state.object}
info={verbConj.info}
handleChange={(payload: { setting: "subject" | "object", person: T.Person }) => dispatch({
type: "setPerson", payload,
})}
handleRandom={(payload: "subject" | "object") => dispatch({
type: "randomPerson", payload,
})}
textOptions={textOptions}
/>
<div className="mt-2 text-center">
<ButtonSelect
options={[
{ label: "Pos.", value: "false" },
{ label: "Neg.", value: "true" },
]}
value={state.negative.toString()}
handleChange={(p) => dispatch({ type: "setNegative", payload: p === "true" })}
/>
</div>
</div>
}
<FormsDisplay
forms={forms}
state={{ ...state, mode: sentencesAvailable ? "chart" : state.mode }}
handleChange={(payload: string) => dispatch({ type: "set forms opened", payload })}
verbConj={verbConj}
textOptions={textOptions}
/>
</div>;
}
function FormsDisplay({ forms, state, handleChange, textOptions, verbConj }: {
verbConj: T.VerbConjugation,
forms: T.DisplayFormItem[],
state: State,
handleChange: (p: string) => void,
textOptions: T.TextOptions,
}) {
function drawLevel(forms: T.DisplayFormItem[], level: number) {
return <div className="mt-3">
{forms.map((f, i) => {
if ("content" in f && f.content.length === 0) {
return null;
}
const showDividerLine = (item: T.DisplayFormItem, index: number): boolean => {
return (index !== 0) && ("content" in item || !item.aspect || (item.aspect === "imperfective"));
};
return <div key={`level1-${i}`}>
{showDividerLine(f, i) && <hr />}
<Hider
label={f.label}
hLevel={5+level}
aspect={"aspect" in f ? f.aspect : undefined}
showing={state.formsOpened.includes(f.label)}
handleChange={() => handleChange(f.label)}
ignore={forms.length === 1}
>
{"content" in f ?
drawLevel(f.content, level + 1)
:
<VerbFormDisplay
displayForm={f}
textOptions={textOptions}
showingFormInfo={state.showingFormInfo}
info={verbConj.info}
/>
}
</Hider>
</div>;
})}
</div>
}
return <div className="mb-2">
{drawLevel(forms, 0)}
</div>;
}
export default ConjugationViewer;

View File

@ -0,0 +1,23 @@
export type Mode = "text" | "blocks";
function ModeSelect({ value, onChange }: { value: Mode, onChange: (m: Mode) => void }) {
return <div style={{ fontSize: "larger", maxWidth: "1.75rem" }}>
{value === "text" ? <div className="clickable" onClick={() => onChange("blocks")}>
<i className="fas fa-cubes" />
</div> : <div className="clickable" onClick={() => onChange("text")}>
<i className="fas fa-align-left" />
</div>}
</div>;
}
export function ScriptSelect({ value, onChange }: { value: "p" | "f", onChange: (m: "p" | "f") => void }) {
return <button style={{ marginLeft: "1.5rem" }} className="btn btn-sm btn-light">
{value === "p" ? <div className="clickable" onClick={() => onChange("f")}>
Ps
</div> : <div className="clickable" onClick={() => onChange("p")}>
Phon.
</div>}
</button>;
}
export default ModeSelect;

View File

@ -0,0 +1,55 @@
import { useState } from "react";
import { filterForVisibleBlocksEP, filterForVisibleBlocksVP } from "../lib/phrase-building/compile";
import * as T from "../types";
import Block from "./blocks/Block";
import KidDisplay from "./blocks/KidDisplay";
function RenderedBlocksDisplay({ opts, rendered, justify, script }: {
script: "p" | "f",
opts: T.TextOptions,
rendered: T.EPRendered | T.VPRendered,
justify?: "left" | "right" | "center",
}) {
const [variation, setVariation] = useState<number>(0);
const blocksWVars = ("omitSubject" in rendered)
? filterForVisibleBlocksEP(rendered.blocks, rendered.omitSubject)
: filterForVisibleBlocksVP(rendered.blocks, rendered.form, rendered.king);
const king = "king" in rendered ? rendered.king : undefined;
const blocks = blocksWVars[variation];
function handleVariationChange() {
setVariation(ov => ((ov + 1) % blocksWVars.length));
}
return <div className={`d-flex flex-row justify-content-${justify ? justify : "center"}`} style={{}}>
<div className={`d-flex flex-row${script === "p" ? "-reverse" : ""} justify-content-left align-items-end mt-3 pb-2`} style={{ overflowX: "auto" }}>
<div key={Math.random()} className="mr-2">
<Block opts={opts} block={blocks[0]} king={king} script={script} />
</div>
<KidsSection opts={opts} kids={rendered.kids} script={script} />
{blocks.slice(1).map((block) => (
<div key={Math.random()} className="mr-2">
<Block opts={opts} block={block} king={king} script={script} />
</div>
))}
<div style={{ height: "100%" }} className="d-flex flex-column justify-content-center">
{blocksWVars.length > 1 && <button onClick={handleVariationChange} className="btn btn-light btn-sm mx-2">V. {variation + 1}/{blocksWVars.length}</button>}
</div>
</div>
</div>
}
function KidsSection({ opts, kids, script }: {
opts: T.TextOptions,
kids: T.Kid[],
script: "p" | "f",
}) {
return kids.length > 0 ? <div className="text-center mx-1 mr-3" style={{ paddingBottom: "1rem"}}>
<div className={`d-flex flex-row${script === "p" ? "-reverse" : ""} mb-3 justify-content-center`}>
{kids.map(kid => (
<KidDisplay key={Math.random()} opts={opts} kid={kid} script={script} />
))}
</div>
<div><strong>kids</strong></div>
</div> : null;
}
export default RenderedBlocksDisplay;

View File

@ -4,89 +4,245 @@ import {
getEnglishFromRendered,
} from "../../lib/phrase-building/np-tools";
import { getEnglishPersonInfo } from "../../library";
import { useState } from "react";
import { getLength } from "../../lib/p-text-helpers";
import { roleIcon } from "../vp-explorer/VPExplorerExplanationModal";
import { negativeParticle } from "../../lib/grammar-units";
function Block({ opts, block }: {
function Block({ opts, block, king, script }: {
opts: T.TextOptions,
block: T.Block,
king?: "subject" | "object" | undefined,
script: "p" | "f";
}) {
if ("equative" in block) {
return <EquativeBlock opts={opts} eq={block.equative} />;
return <EquativeBlock opts={opts} eq={block.equative} script={script} />;
}
if (block.type === "AP") {
const english = getEnglishFromRendered(block);
return <APBlock opts={opts} english={english}>{block}</APBlock>
return <APBlock opts={opts} english={english} script={script}>{block}</APBlock>
}
if (block.type === "subjectSelection") {
return <SubjectBlock opts={opts} np={block.selection} />
const role = king === "subject" ? "king" : king === "object" ? "servant" : undefined;
return <SubjectBlock opts={opts} np={block.selection} role={role} script={script} />
}
if (block.type === "predicateSelection") {
const english = getEnglishFromRendered(block.selection);
return <div className="text-center">
<div><strong>Predicate</strong></div>
{block.selection.type === "EQComp"
? <EqCompBlock opts={opts} comp={block.selection.selection} />
: <NPBlock opts={opts} english={english}>{block.selection}</NPBlock>}
? <EqCompBlock opts={opts} comp={block.selection.selection} script={script} />
: <NPBlock opts={opts} english={english} script={script}>{block.selection}</NPBlock>}
</div>
}
if (block.type === "nu") {
return <NUBlock opts={opts} />
if (block.type === "negative") {
return <NegBlock opts={opts} imperative={block.imperative} script={script} />
}
if (block.type === "perfectiveHead") {
return <PerfHeadBlock opts={opts} ps={block.ps} script={script} />
}
if (block.type === "verbComplement") {
return <VCompBlock opts={opts} comp={block.complement} script={script} />;
}
if (block.type === "verb") {
return <VerbSBlock opts={opts} v={block.block} script={script} />;
}
if (block.type === "objectSelection") {
const role = king === "object" ? "king" : king === "subject" ? "servant" : undefined;
return <ObjectBlock opts={opts} obj={block.selection} role={role} script={script} />;
}
if (block.type === "perfectParticipleBlock") {
return <VerbSBlock opts={opts} v={block} script={script} />;
}
if (block.type === "perfectEquativeBlock") {
return <EquativeBlock opts={opts} eq={block} script={script} />;
}
if (block.type === "modalVerbBlock") {
return <ModalVerbBlock opts={opts} v={block} script={script} />;
}
if (block.type === "modalVerbKedulPart") {
return <ModalAuxBlock opts={opts} aux={block} script={script} />
}
return null;
}
export default Block;
function NUBlock({ opts }: {
function Border({ children, extraClassName, padding }: { children: JSX.Element | JSX.Element[] | string, extraClassName?: string, padding?: string }) {
return <div
className={`d-flex flex-row justify-content-center align-items-center ${extraClassName ? extraClassName : ""}`}
style={{
border: "2px solid black",
padding: padding ? padding : "1rem",
textAlign: "center",
}}
>
<>{children}</>
</div>
}
function VerbSBlock({ opts, v, script }: {
opts: T.TextOptions,
script: "p" | "f",
v: T.VerbRenderedBlock["block"] | T.PerfectParticipleBlock,
}) {
const [length, setLength] = useState<T.Length>("long");
function changeLength() {
setLength(o => (
o === "long"
? "short"
: o === "short" && "mini" in v.ps
? "mini"
: "long"
));
}
return <div className="text-center">
{"long" in v.ps && <div className="clickable small mb-1" onClick={changeLength}>{length}</div>}
<Border>
{getLength(v.ps, length)[0][script]}
</Border>
<div>{v.type === "perfectParticipleBlock" ? "Past Partic." : "Verb"}</div>
<EnglishBelow>{getEnglishPersonInfo(v.person, "short")}</EnglishBelow>
</div>
}
function ModalVerbBlock({ opts, v, script }: {
opts: T.TextOptions,
script: "p" | "f",
v: T.ModalVerbBlock,
}) {
const [length, setLength] = useState<T.Length>("long");
function changeLength() {
setLength(o => (
o === "long"
? "short"
: "long"
));
}
return <div className="text-center">
{"long" in v.ps && <div className="clickable small mb-1" onClick={changeLength}>{length}</div>}
<Border>
{getLength(v.ps, length)[0][script]}
</Border>
<div>Verb</div>
<EnglishBelow>Modal</EnglishBelow>
</div>
}
function PerfHeadBlock({ opts, ps, script }: {
opts: T.TextOptions,
ps: T.PsString,
script: "p" | "f",
}) {
return <div className="text-center">
<div
className={classNames("d-flex flex-row justify-content-center align-items-center")}
style={{
border: "2px solid black",
padding: "1rem",
textAlign: "center",
}}
>
nu
</div>
<Border>
{ps[script]}
</Border>
<div>perf. head</div>
<EnglishBelow>{'\u00A0'}</EnglishBelow>
</div>;
}
function VCompBlock({ opts, comp, script }: {
opts: T.TextOptions,
comp: T.VerbComplementBlock["complement"],
script: "p" | "f",
}) {
return <div className="text-center">
<Border>
{comp[script]}
</Border>
<div>Complement</div>
<EnglishBelow>{'\u00A0'}</EnglishBelow>
</div>;
}
function ModalAuxBlock({ opts, aux, script }: {
opts: T.TextOptions,
aux: T.ModalVerbKedulPart,
script: "p" | "f",
}) {
return <div className="text-center">
<Border>
{aux.ps[0][script]}
</Border>
<div>Modal Aux</div>
<EnglishBelow>{getEnglishPersonInfo(aux.verb.block.person, "short")}</EnglishBelow>
</div>;
}
function NegBlock({ opts, imperative, script }: {
opts: T.TextOptions,
imperative: boolean,
script: "p" | "f",
}) {
return <div className="text-center">
<Border>
{negativeParticle[imperative ? "imperative" : "nonImperative"][script]}
</Border>
<div>Neg.</div>
<EnglishBelow>not</EnglishBelow>
<EnglishBelow>{imperative ? "don't" : "not"}</EnglishBelow>
</div>;
}
function EquativeBlock({ opts, eq }: {
function EquativeBlock({ opts, eq, script }: {
opts: T.TextOptions,
eq: T.EquativeRendered,
eq: T.EquativeRendered | T.PerfectEquativeBlock,
script: "p" | "f",
}) {
const [length, setLength] = useState<T.Length>("long");
function changeLength() {
setLength(o => (
o === "long"
? "short"
: o === "short" && "mini" in eq.ps
? "mini"
: "long"
));
}
return <div className="text-center">
<div
className={classNames("d-flex flex-row justify-content-center align-items-center")}
style={{
border: "2px solid black",
padding: "1rem",
textAlign: "center",
}}
>
{"short" in eq.ps ? eq.ps.short[0].f : eq.ps[0].f}
</div>
{"long" in eq.ps && <div className="clickable small mb-1" onClick={changeLength}>{length}</div>}
<Border>
{getLength(eq.ps, length)[0][script]}
</Border>
<div>Equative</div>
<EnglishBelow>{"="}</EnglishBelow>
<EnglishBelow>{getEnglishPersonInfo(eq.person, "short")}</EnglishBelow>
</div>;
}
function SubjectBlock({ opts, np }: {
function SubjectBlock({ opts, np, role, script }: {
opts: T.TextOptions,
np: T.Rendered<T.NPSelection>,
role: "king" | "servant" | undefined,
script: "p" | "f",
}) {
const english = getEnglishFromRendered(np);
return <div className="text-center">
<div><strong>Subject</strong></div>
<NPBlock opts={opts} english={english}>{np}</NPBlock>
<div><strong>Subject</strong>{role ? roleIcon[role] : ""}</div>
<NPBlock opts={opts} english={english} script={script}>{np}</NPBlock>
</div>;
}
function EqCompBlock({ opts, comp }: {
function ObjectBlock({ opts, obj, role, script }: {
opts: T.TextOptions,
obj: T.Rendered<T.ObjectSelectionComplete>["selection"],
role: "king" | "servant" | undefined,
script: "p" | "f",
}) {
if (typeof obj !== "object") {
return null;
}
const english = getEnglishFromRendered(obj);
return <div className="text-center">
<div><strong>Object</strong>{role ? roleIcon[role] : ""}</div>
<NPBlock opts={opts} english={english} script={script}>{obj}</NPBlock>
</div>;
}
function EqCompBlock({ opts, comp, script }: {
script: "p" | "f",
opts: T.TextOptions,
comp: T.Rendered<T.EqCompSelection["selection"]>,
}) {
@ -95,16 +251,9 @@ function EqCompBlock({ opts, comp }: {
adj: T.Rendered<T.AdjectiveSelection>,
}) {
return <div className="text-center">
<div
className={classNames("d-flex flex-row justify-content-center align-items-center")}
style={{
border: "2px solid black",
padding: "1rem",
textAlign: "center",
}}
>
{adj.ps[0].f}
</div>
<Border>
{adj.ps[0][script]}
</Border>
<div>Adj.</div>
<EnglishBelow>{adj.e}</EnglishBelow>
</div>;
@ -115,16 +264,9 @@ function EqCompBlock({ opts, comp }: {
adv: T.Rendered<T.LocativeAdverbSelection>,
}) {
return <div className="text-center">
<div
className={classNames("d-flex flex-row justify-content-center align-items-center")}
style={{
border: "2px solid black",
padding: "1rem",
textAlign: "center",
}}
>
{adv.ps[0].f}
</div>
<Border>
{adv.ps[0][script]}
</Border>
<div>Loc. Adv.</div>
<EnglishBelow>{adv.e}</EnglishBelow>
</div>;
@ -137,89 +279,78 @@ function EqCompBlock({ opts, comp }: {
: comp.type === "loc. adv."
? <LocAdvBlock opts={opts} adv={comp} />
: <div>
<Sandwich opts={opts} sandwich={comp} />
<Sandwich opts={opts} sandwich={comp} script={script} />
<div>Sandwich</div>
<EnglishBelow>{comp.e}</EnglishBelow>
</div>}
</div>;
}
export function APBlock({ opts, children, english }: {
export function APBlock({ opts, children, english, script }: {
opts: T.TextOptions,
children: T.Rendered<T.APSelection>,
english?: string,
script: "p" | "f",
}) {
const ap = children;
if (ap.selection.type === "adverb") {
return <div className="text-center">
<div
className={classNames("d-flex flex-row justify-content-center align-items-center")}
style={{
border: "2px solid black",
padding: "1rem",
textAlign: "center",
}}
>
{ap.selection.ps[0].f}
</div>
<Border>
{ap.selection.ps[0][script]}
</Border>
<div>AP</div>
<EnglishBelow>{english}</EnglishBelow>
</div>;
}
return <div>
<Sandwich opts={opts} sandwich={ap.selection} />
<Sandwich opts={opts} sandwich={ap.selection} script={script} />
<div>AP</div>
<EnglishBelow>{english}</EnglishBelow>
</div>;
}
function Sandwich({ opts, sandwich }: {
function Sandwich({ opts, sandwich, script }: {
opts: T.TextOptions,
sandwich: T.Rendered<T.SandwichSelection<T.Sandwich>>,
script: "p" | "f",
}) {
return <div className="text-center">
<div className="text-center">Sandwich 🥪</div>
<div
className={classNames("d-flex flex-row justify-content-center align-items-center")}
style={{
border: "2px solid black",
padding: "0.75rem 0.5rem 0.25rem 0.5rem",
textAlign: "center",
}}
>
<div className="d-flex flex-row justify-content-between align-items-end">
<Possesors opts={opts}>{sandwich.inside.selection.type !== "pronoun" ? sandwich.inside.selection.possesor : undefined}</Possesors>
<Border padding="0.75rem 0.5rem 0.25rem 0.5rem">
<div className={`d-flex flex-row${script === "p" ? "-reverse" : ""} justify-content-between align-items-end`}>
<Possesors opts={opts} script={script}>{sandwich.inside.selection.type !== "pronoun" ? sandwich.inside.selection.possesor : undefined}</Possesors>
<div className="mr-2 ml-1 mb-1"><strong>{sandwich.before ? sandwich.before.f : ""}</strong></div>
<div>
<NPBlock opts={opts} inside>{sandwich.inside}</NPBlock>
<NPBlock opts={opts} inside script={script}>{sandwich.inside}</NPBlock>
</div>
<div className="ml-2 mr-1 mb-1"><strong>{sandwich.after ? sandwich.after.f : ""}</strong></div>
</div>
</div>
</Border>
</div>;
}
export function NPBlock({ opts, children, inside, english }: {
export function NPBlock({ opts, children, inside, english, script }: {
opts: T.TextOptions,
children: T.Rendered<T.NPSelection>,
inside?: boolean,
english?: string,
script: "p" | "f",
}) {
const np = children;
const hasPossesor = !!(np.selection.type !== "pronoun" && np.selection.possesor && !np.selection.possesor.shrunken);
const elements = [
...!inside ? [<Possesors opts={opts} script={script}>{np.selection.type !== "pronoun" ? np.selection.possesor : undefined}</Possesors>] : [],
<Adjectives opts={opts} script={script}>{np.selection.adjectives}</Adjectives>,
<div className={np.selection.adjectives?.length ? "mx-1" : ""}> {np.selection.ps[0][script]}</div>,
];
const el = script === "p" ? elements.reverse() : elements;
return <div className="text-center">
<div
className={classNames("d-flex flex-row justify-content-center align-items-center", { "pt-2": !inside && hasPossesor })}
style={{
border: "2px solid black",
padding: inside ? "0.3rem" : hasPossesor ? "0.5rem 1rem 0.25rem 1rem" : "1rem",
textAlign: "center",
}}
<Border
extraClassName={`!inside && hasPossesor ? "pt-2" : ""`}
padding={inside ? "0.3rem" : hasPossesor ? "0.5rem 0.8rem 0.25rem 0.8rem" : "1rem"}
>
{!inside && <Possesors opts={opts}>{np.selection.type !== "pronoun" ? np.selection.possesor : undefined}</Possesors>}
<Adjectives opts={opts}>{np.selection.adjectives}</Adjectives>
<div> {np.selection.ps[0].f}</div>
</div>
{el}
</Border>
<div className={inside ? "small" : ""}>
NP
{!inside ? <>
@ -227,13 +358,14 @@ export function NPBlock({ opts, children, inside, english }: {
<span className="text-muted small">({getEnglishPersonInfo(np.selection.person, "short")})</span>
</> : <></>}
</div>
<EnglishBelow>{english}</EnglishBelow>
{!inside && <EnglishBelow>{english}</EnglishBelow>}
</div>
}
function Possesors({ opts, children }: {
function Possesors({ opts, children, script }: {
opts: T.TextOptions,
children: { shrunken: boolean, np: T.Rendered<T.NPSelection> } | undefined,
script: "p" | "f",
}) {
if (!children) {
return null;
@ -241,18 +373,18 @@ function Possesors({ opts, children }: {
if (children.shrunken) {
return null;
}
const contraction = checkForContraction(children.np);
return <div className="d-flex flex-row mr-1 align-items-end" style={{
const contraction = checkForContraction(children.np, script);
return <div className={`d-flex flex-row${script === "p" ? "-reverse" : ""} mr-1 align-items-end`} style={{
marginBottom: "0.5rem",
borderBottom: "1px solid grey",
}}>
{children.np.selection.type !== "pronoun" && <Possesors opts={opts}>{children.np.selection.possesor}</Possesors>}
{children.np.selection.type !== "pronoun" && <Possesors opts={opts} script={script}>{children.np.selection.possesor}</Possesors>}
<div>
{contraction && <div className="mb-1">({contraction})</div>}
<div className={classNames("d-flex", "flex-row", "align-items-center", { "text-muted": contraction })}>
<div className="mr-1 pb-2">du</div>
<div className={classNames("d-flex", (script === "f" ? "flex-row" : "flex-row-reverse"), "align-items-center", { "text-muted": contraction })}>
<div className="mx-1 pb-2">{script === "p" ? "د" : "du"}</div>
<div>
<NPBlock opts={opts} inside>{children.np}</NPBlock>
<NPBlock script={script} opts={opts} inside>{children.np}</NPBlock>
</div>
</div>
@ -260,32 +392,19 @@ function Possesors({ opts, children }: {
</div>
}
function checkForContraction(np: T.Rendered<T.NPSelection>): string | undefined {
if (np.selection.type !== "pronoun") return undefined;
if (np.selection.person === T.Person.FirstSingMale || np.selection.person === T.Person.FirstSingFemale) {
return "zmaa"
}
if (np.selection.person === T.Person.SecondSingMale || np.selection.person === T.Person.SecondSingFemale) {
return "staa"
}
if (np.selection.person === T.Person.FirstPlurMale || np.selection.person === T.Person.FirstPlurFemale) {
return "zmoonG"
}
if (np.selection.person === T.Person.SecondPlurMale || np.selection.person === T.Person.SecondPlurFemale) {
return "staaso"
}
return undefined;
}
function Adjectives({ opts, children }: {
function Adjectives({ opts, children, script }: {
opts: T.TextOptions,
children: T.Rendered<T.AdjectiveSelection>[] | undefined,
script: "p" | "f",
}) {
if (!children) {
return null;
}
const c = script === "p"
? children.reverse()
: children;
return <em className="mr-1">
{children.map(a => a.ps[0].f).join(" ")}{` `}
{c.map(a => a.ps[0][script]).join(" ")}{` `}
</em>
}
@ -296,3 +415,21 @@ function EnglishBelow({ children: e }: { children: string | undefined }) {
height: "1rem",
}}>{e ? e : ""}</div>;
}
function checkForContraction(np: T.Rendered<T.NPSelection>, script: "p" | "f"): string | undefined {
if (np.selection.type !== "pronoun") return undefined;
if (np.selection.person === T.Person.FirstSingMale || np.selection.person === T.Person.FirstSingFemale) {
return script === "f" ? "zmaa" : "زما";
}
if (np.selection.person === T.Person.SecondSingMale || np.selection.person === T.Person.SecondSingFemale) {
return script === "f" ? "staa" : "ستا";
}
if (np.selection.person === T.Person.FirstPlurMale || np.selection.person === T.Person.FirstPlurFemale) {
return script === "f" ? "zmoonG" : "زمونږ";
}
if (np.selection.person === T.Person.SecondPlurMale || np.selection.person === T.Person.SecondPlurFemale) {
return script === "f" ? "staaso" : "ستاسو";
}
return undefined;
}

View File

@ -1,15 +1,20 @@
import { baParticle } from "../../lib/grammar-units";
import * as T from "../../types";
import Pashto from "../Pashto";
import Phonetics from "../Phonetics";
function KidDisplay({ opts, kid }: {
function KidDisplay({ opts, kid, script }: {
opts: T.TextOptions,
kid: T.Kid,
script: "p" | "f",
}) {
const ps = kid.type === "ba"
? baParticle
: kid.ps;
return <div className="mx-1">
{kid.type === "ba"
? <Phonetics opts={opts}>{baParticle}</Phonetics>
: <Phonetics opts={opts}>{kid.ps}</Phonetics>}
{script === "p"
? <Pashto opts={opts}>{ps}</Pashto>
: <Phonetics opts={opts}>{ps}</Phonetics>}
</div>
}

View File

@ -1,42 +0,0 @@
import * as T from "../../types";
import Block from "../blocks/Block";
import KidDisplay from "../blocks/KidDisplay";
function EPBlocksDisplay({ opts, rendered, justify }: {
opts: T.TextOptions,
rendered: T.EPRendered,
justify?: "left" | "right" | "center",
}) {
const blocks = rendered.omitSubject
? rendered.blocks.filter(b => b.type !== "subjectSelection")
: rendered.blocks;
return <div className={`d-flex flex-row justify-content-${justify ? justify : "center"}`} style={{}}>
<div className="d-flex flex-row justify-content-left align-items-end mt-3 pb-2" style={{ overflowX: "auto" }}>
<div key={Math.random()} className="mr-2">
<Block opts={opts} block={blocks[0]} />
</div>
<KidsSection opts={opts} kids={rendered.kids} />
{blocks.slice(1).map((block, i) => (
<div key={Math.random()} className="mr-2">
<Block opts={opts} block={block} />
</div>
))}
</div>
</div>
}
function KidsSection({ opts, kids }: {
opts: T.TextOptions,
kids: T.Kid[],
}) {
return kids.length > 0 ? <div className="text-center mx-1 mr-3" style={{ paddingBottom: "1rem"}}>
<div className="d-flex flex-row mb-3 justify-content-center">
{kids.map(kid => (
<KidDisplay key={Math.random()} opts={opts} kid={kid} />
))}
</div>
<div><strong>kids</strong></div>
</div> : null;
}
export default EPBlocksDisplay;

View File

@ -4,9 +4,10 @@ import { compileEP } from "../../lib/phrase-building/compile";
import ButtonSelect from "../ButtonSelect";
import { getPredicateSelectionFromBlocks, getSubjectSelection, getSubjectSelectionFromBlocks } from "../../lib/phrase-building/blocks-utils";
import { useState } from "react";
import EPTextDisplay from "./EPTextDisplay";
import EPBlocksDisplay from "./EPBlocksDisplay";
type Mode = "text" | "blocks";
import CompiledPTextDisplay from "../CompiledPTextDisplay";
import EPBlocksDisplay from "../RenderedBlocksDisplay";
import ModeSelect, { Mode, ScriptSelect } from "../DisplayModeSelect";
import { useStickyState } from "../../library";
function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne }: {
eps: T.EPSelectionState,
@ -16,6 +17,7 @@ function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne }: {
onlyOne?: boolean,
}) {
const [mode, setMode] = useState<Mode>("text");
const [script, setScript] = useStickyState<"p" | "f">("f", "blockScriptChoice");
const EP = completeEPSelection(eps);
const subject = getSubjectSelection(eps.blocks);
@ -36,7 +38,10 @@ function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne }: {
const renderedPredicate = getPredicateSelectionFromBlocks(rendered.blocks).selection;
return <div className="text-center pt-3">
<div className="mb-2 d-flex flex-row justify-content-between align-items-center">
<ModeSelect value={mode} onChange={setMode} />
<div className="d-flex flex-row">
<ModeSelect value={mode} onChange={setMode} />
{mode === "blocks" && <ScriptSelect value={script} onChange={setScript} />}
</div>
{setOmitSubject !== false ? <ButtonSelect
small
value={(eps.omitSubject ? "true" : "false") as "true" | "false"}
@ -49,8 +54,8 @@ function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne }: {
<div />
</div>
{mode === "text"
? <EPTextDisplay opts={opts} compiled={result} justify={justify} onlyOne={onlyOne} />
: <EPBlocksDisplay opts={opts} rendered={rendered} justify={justify} />}
? <CompiledPTextDisplay opts={opts} compiled={result} justify={justify} onlyOne={onlyOne} />
: <EPBlocksDisplay opts={opts} rendered={rendered} justify={justify} script={script} />}
{result.e && <div className={`text-muted mt-2 text-${justify === "left" ? "left" : justify === "right" ? "right" : "center"}`}>
{(onlyOne ? [result.e[0]] : result.e).map((e, i) => <div key={i}>{e}</div>)}
</div>}
@ -63,14 +68,4 @@ function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne }: {
</div>
}
function ModeSelect({ value, onChange }: { value: Mode, onChange: (m: Mode) => void }) {
return <div style={{ fontSize: "larger" }}>
{value === "text" ? <div className="clickable" onClick={() => onChange("blocks")}>
<i className="fas fa-cubes" />
</div> : <div className="clickable" onClick={() => onChange("text")}>
<i className="fas fa-align-left" />
</div>}
</div>;
}
export default EPDisplay;

View File

@ -4,7 +4,7 @@ import {
personNumber,
} from "../../lib/misc-helpers";
import { isUnisexNounEntry } from "../../lib/type-predicates";
import { checkEPForMiniPronounsError } from "../../lib/phrase-building/compile";
import { checkForMiniPronounsError } from "../../lib/phrase-building/compile";
import { adjustSubjectSelection, getSubjectSelection, insertNewAP, removeAP, setAP, shiftBlock } from "../../lib/phrase-building/blocks-utils";
export type EpsReducerAction = {
@ -189,7 +189,7 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
}
function ensureMiniPronounsOk(old: T.EPSelectionState, eps: T.EPSelectionState, sendAlert?: (msg: string) => void): T.EPSelectionState {
const error = checkEPForMiniPronounsError(eps);
const error = checkForMiniPronounsError(eps);
if (error) {
if (sendAlert) sendAlert(error);
return old;

View File

@ -1,56 +1,55 @@
import { compileVP } from "../../lib/phrase-building/compile";
import * as T from "../../types";
import AbbreviationFormSelector from "./AbbreviationFormSelector";
import Examples from "../Examples";
import { getObjectSelection, getSubjectSelection } from "../../lib/phrase-building/blocks-utils";
import { completeVPSelection } from "../../lib/phrase-building/vp-tools";
import { renderVP } from "../../library";
import ModeSelect, { Mode, ScriptSelect } from "../DisplayModeSelect";
import { useState } from "react";
import CompiledPTextDisplay from "../CompiledPTextDisplay";
import RenderedBlocksDisplay from "../RenderedBlocksDisplay";
function VPDisplay({ VP, opts, setForm }: {
VP: T.VPSelectionState | T.VPRendered,
function VPDisplay({ VPS, opts, setForm, justify, onlyOne }: {
VPS: T.VPSelectionState,
opts: T.TextOptions,
setForm: (form: T.FormVersion) => void,
justify?: "left" | "right" | "center",
onlyOne?: boolean,
}) {
if (!("type" in VP)) {
const [mode, setMode] = useState<Mode>("text");
const [script, setScript] = useState<"p" | "f">("f");
const VP = completeVPSelection(VPS);
if (!VP) {
return <div className="lead text-muted text-center mt-4">
{(() => {
const subject = getSubjectSelection(VP.blocks).selection;
const object = getObjectSelection(VP.blocks).selection;
if (subject === undefined || object || undefined) {
const subject = getSubjectSelection(VPS.blocks).selection;
const object = getObjectSelection(VPS.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 });
const rendered = renderVP(VP);
const result = compileVP(rendered, rendered.form);
return <div className="text-center mt-1">
<AbbreviationFormSelector
adjustable={VP.whatsAdjustable}
form={VP.form}
adjustable={rendered.whatsAdjustable}
form={rendered.form}
onChange={setForm}
/>
{"long" in result.ps ?
<div>
{/* <div className="h6">Long Verb:</div> */}
<VariationLayer vs={result.ps.long} opts={opts} />
{/* <div className="h6">Short Verb:</div> */}
<VariationLayer vs={result.ps.short} opts={opts} />
{result.ps.mini && <>
{/* <div className="h6">Mini Verb:</div> */}
<VariationLayer vs={result.ps.mini} opts={opts} />
</>}
</div>
: <VariationLayer vs={result.ps} opts={opts} />
}
<div className="d-flex flex-row">
<ModeSelect value={mode} onChange={setMode} />
{mode === "blocks" && <ScriptSelect value={script} onChange={setScript} />}
</div>
{mode === "text"
? <CompiledPTextDisplay opts={opts} compiled={result} justify={justify} onlyOne={onlyOne} />
: <RenderedBlocksDisplay opts={opts} rendered={rendered} justify={justify} script={script} />}
{result.e && <div className="text-muted mt-3">
{result.e.map((e, i) => <div key={i}>{e}</div>)}
</div>}
</div>
}
function VariationLayer({ vs, opts }: { vs: T.PsString[], opts: T.TextOptions }) {
return <div className="mb-2">
<Examples opts={opts} lineHeight={0}>{vs}</Examples>
</div>;
}
export default VPDisplay;

View File

@ -15,7 +15,6 @@ import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationMod
// @ts-ignore
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";
@ -44,7 +43,7 @@ function VPExplorer(props: {
props.loaded
? props.loaded
: savedVps => makeVPSelectionState(props.verb, savedVps),
"vpsState14",
"vpsState15",
flashMessage,
);
const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">(
@ -143,7 +142,7 @@ function VPExplorer(props: {
const VPS = completeVPSelection(vps);
const phraseIsComplete = !!VPS;
const rendered = VPS ? renderVP(VPS) : undefined;
const servantIsShrunk = !!(rendered ? getShrunkenServant(rendered) : undefined);
const servantIsShrunk = includesShrunkenServant(rendered?.kids);
function toggleServantShrink() {
adjustVps({
type: "toggle servant shrink",
@ -257,7 +256,12 @@ function VPExplorer(props: {
: (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>
? <div>
{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</div>}
<div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
</div>
: <NPPicker
phraseIsComplete={phraseIsComplete}
heading={roles.king === "object"
@ -295,7 +299,7 @@ function VPExplorer(props: {
</div>
</div>}
{mode === "phrases" && <VPDisplay
VP={rendered ? rendered : vps}
VPS={vps}
opts={props.opts}
setForm={handleSetForm}
/>}
@ -351,3 +355,10 @@ function getVPSFromUrl(): T.VPSelectionState | undefined {
return JSON.parse(decoded) as T.VPSelectionState;
}
function includesShrunkenServant(kids?: T.Kid[]): boolean {
if (!kids) return false;
return kids.some(k => (
k.type === "mini-pronoun" && k.source === "servant"
));
}

View File

@ -15,10 +15,10 @@ import playAudio from "../../lib/play-audio";
import TensePicker from "./TensePicker";
import Keyframes from "../Keyframes";
import energyDrink from "./energy-drink.jpg";
import { flattenLengths } from "../../lib/phrase-building/segment";
import { flattenLengths } from "../../lib/phrase-building/compile";
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";
import { adjustObjectSelection, adjustSubjectSelection, getObjectSelection, getObjectSelectionFromBlocks, getSubjectSelectionFromBlocks, getSubjectSelection, getVerbAndHeadFromBlocks } from "../../lib/phrase-building/blocks-utils";
const correctEmoji = ["✅", '🤓', "✅", '😊', "🌹", "✅", "✅", '🥳', "👏", "✅", "💯", "😎", "✅", "👍"];
@ -113,8 +113,8 @@ function VPExplorerQuiz(props: {
}
}
const rendered = renderVP(quizState.vps);
const subject: T.Rendered<T.NPSelection> = getRenderedSubjectSelection(rendered.blocks).selection;
const object = getRenderedObjectSelection(rendered.blocks).selection;
const subject: T.Rendered<T.NPSelection> = getSubjectSelectionFromBlocks(rendered.blocks).selection;
const object = getObjectSelectionFromBlocks(rendered.blocks).selection;
const { e } = compileVP(rendered, { removeKing: false, shrinkServant: false });
return <div className="mt-4">
<ProgressBar quizState={quizState} />
@ -346,18 +346,17 @@ function tickQuizState(startingWith: T.VPSelectionComplete | QuizState): QuizSta
}
function getBlanksAnswer(vps: T.VPSelectionComplete): { ps: T.PsString[], withBa: boolean } {
const { verb } = renderVP(vps);
const { head, rest } = verb.ps;
const ps = flattenLengths(rest).map(x => {
const { verb, perfectiveHead } = getVerbAndHeadFromBlocks(renderVP(vps).blocks);
const ps = flattenLengths(verb.block.ps).map(x => {
const y = removeBa(x);
if (head) {
return concatPsString(head, y);
if (perfectiveHead) {
return concatPsString(perfectiveHead.ps, y);
}
return y;
});
return {
ps,
withBa: verb.hasBa,
withBa: verb.block.hasBa,
}
}

View File

@ -1,518 +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.
*
*/
import {
ensureBaAt,
isAllOne,
isVerbBlock,
removeHead,
uniquePsStringArray,
splitOffLeapfrogWord,
removeObjComp,
psRemove,
psStringContains,
} from "../lib/p-text-helpers";
import {
getPersonFromVerbForm,
pickPersInf,
} from "../lib/misc-helpers";
import {
baParticle,
pronouns,
} from "../lib/grammar-units";
import {
removeAccents,
} from "../lib/accent-helpers";
import { concatPsString } from "../lib/p-text-helpers";
import * as T from "../types";
const pashtoCharacterRange = "\u0621-\u065f\u0670-\u06d3\u06d5"
function getSplitHead(split: T.SplitInfo | undefined, matrixKey: T.PersonInflectionsField) {
if (!split) {
return undefined;
}
const fromMatrix = pickPersInf(split, matrixKey)
// doesn't matter what length it is, the head will always be the same
const pair = "long" in fromMatrix ? fromMatrix.long : fromMatrix;
return pair[0];
}
function formHasVariations(form: T.VerbForm | T.ImperativeForm | T.ParticipleForm | T.SentenceForm): boolean {
if ("mascSing" in form) {
return formHasVariations(form.mascSing);
}
if ("long" in form) {
return formHasVariations(form.long);
}
if (!isVerbBlock(form)) {
return false;
}
return !isAllOne(form);
}
type Pronouns = undefined | {
subject: T.PsString | [T.PsString, T.PsString],
object?: T.PsString | [T.PsString, T.PsString],
mini: T.PsString,
}
const nuParticle = { p: "نه", f: "nú" };
export default function addPronouns({ s, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel = "hard" }: {
s: T.SentenceForm,
subject: T.Person,
object: T.Person,
info: T.NonComboVerbInfo,
displayForm: T.DisplayFormForSentence,
intransitive: boolean,
ergative: boolean,
matrixKey: T.PersonInflectionsField,
negative: boolean,
englishConjugation?: T.EnglishVerbConjugation,
sentenceLevel?: "easy" | "medium" | "hard",
}): T.SentenceForm {
if ("long" in s) {
return {
long: addPronouns({ s: s.long, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel }) as T.ArrayOneOrMore<T.PsString>,
short: addPronouns({ s: s.short, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel }) as T.ArrayOneOrMore<T.PsString>,
...s.mini ? {
mini: addPronouns({ s: s.mini, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel }) as T.ArrayOneOrMore<T.PsString>,
} : {},
}
}
function makeEnglish(englishBuilder: T.EnglishBuilder, englishConjugation: T.EnglishVerbConjugation): string[] {
const noObject = (intransitive || info.transitivity === "grammatically transitive" || info.type === "dynamic compound");
const addRest = (s: string) => (
`${s}${noObject ? "" : ` ${engObj(object)}`}${englishConjugation.ep ? ` ${englishConjugation.ep}` : ""}`
);
return englishBuilder(subject, englishConjugation.ec, negative)
.map(addRest);
}
const firstOrSecondObjectPresent = [0,1,2,3,6,7,8,9].includes(object) && !displayForm.past;
const nearPronounPossible = (p: T.Person) => [4, 5, 10, 11].includes(p);
const noPronouns =
info.transitivity === "grammatically transitive" && displayForm.passive;
const noObjectPronoun =
intransitive || info.transitivity === "grammatically transitive" ||
info.type === "dynamic compound" || info.type === "generative stative compound";
const transDynCompPast =
info.transitivity === "transitive" && info.type === "dynamic compound" && displayForm.past;
const subjectPronoun = (getPersonFromVerbForm(
pronouns.far[ergative ? "inflected" : "plain"],
subject,
) as T.ArrayOneOrMore<T.PsString>)[0];
const nearSubjectPronoun = (getPersonFromVerbForm(
pronouns.near[ergative ? "inflected" : "plain"],
subject,
) as T.ArrayOneOrMore<T.PsString>)[0];
const objectPronoun = (getPersonFromVerbForm(
pronouns.far[firstOrSecondObjectPresent ? "inflected" : "plain"],
object,
) as T.ArrayOneOrMore<T.PsString>)[0];
const nearObjectPronoun = (getPersonFromVerbForm(
pronouns.near[firstOrSecondObjectPresent ? "inflected" : "plain"],
object,
) as T.ArrayOneOrMore<T.PsString>)[0];
const miniPronoun = (getPersonFromVerbForm(
pronouns.mini,
ergative ? subject : object,
) as T.ArrayOneOrMore<T.PsString>)[0];
const prns: Pronouns = noPronouns
? undefined
: noObjectPronoun
? {
subject: ((sentenceLevel === "hard") && nearPronounPossible(subject)) ? [subjectPronoun, nearSubjectPronoun] : subjectPronoun,
mini: miniPronoun,
} : {
subject: ((sentenceLevel === "hard") && nearPronounPossible(subject)) ? [subjectPronoun, nearSubjectPronoun] : subjectPronoun,
object: ((sentenceLevel === "hard") && nearPronounPossible(object)) ? [objectPronoun, nearObjectPronoun] : objectPronoun,
mini: miniPronoun,
};
const english = (displayForm.englishBuilder && englishConjugation)
? makeEnglish(displayForm.englishBuilder, englishConjugation)
: undefined;
function attachPronounsToVariation(ps: T.PsString, prns: Pronouns): T.ArrayOneOrMore<T.PsString> {
if (!prns) {
return [ps];
}
if (Array.isArray(prns.subject)) {
return [
...attachPronounsToVariation(ps, { ...prns, subject: prns.subject[0] }),
...attachPronounsToVariation(ps, { ...prns, subject: prns.subject[1] }),
] as T.ArrayOneOrMore<T.PsString>;
}
if (Array.isArray(prns.object)) {
return [
...attachPronounsToVariation(ps, { ...prns, object: prns.object[0] }),
...attachPronounsToVariation(ps, { ...prns, object: prns.object[1] }),
] as T.ArrayOneOrMore<T.PsString>;
}
const splitHead = (displayForm.aspect && displayForm.aspect === "perfective")
? getSplitHead(info[displayForm.past ? "root" : "stem"].perfectiveSplit, matrixKey)
: undefined;
const basicForms = (!prns.object)
// basic form with only one pronoun
? makeBasicPronounForm(ps, splitHead, displayForm, info, negative, prns.subject)
: [
// basic form two full pronouns
...makeBasicPronounForm(ps, splitHead, displayForm, info, negative, prns.subject, prns.object),
// basic form one full, one mini pronoun
...sentenceLevel !== "easy" ? makeBasicPronounForm(
ps,
splitHead,
displayForm,
info,
negative,
ergative ? prns.object : prns.subject,
prns.mini,
) : [],
] as T.ArrayOneOrMore<T.PsString>;
const ergativeGrammTrans = (info.transitivity === "grammatically transitive" && ergative);
const canWorkWithOnlyMini = (prns.object && !displayForm.secondPronounNeeded && formHasVariations(displayForm.form))
|| transDynCompPast || ergativeGrammTrans;
return [
...basicForms,
...(sentenceLevel !== "easy" && canWorkWithOnlyMini)
? makeOnlyMiniForm(ps, splitHead, displayForm, info, negative, prns.mini)
: [],
].map((ps) => english ? { ...ps, e: english } : ps) as T.ArrayOneOrMore<T.PsString>;
}
// @ts-ignore
return s.reduce((variations, current) => (
[...variations, ...uniquePsStringArray(
attachPronounsToVariation(current, prns)
)]
), []) as T.ArrayOneOrMore<T.PsString>;
}
function nuMustGoAfterSplitHead(head: T.PsString) {
return (
["و", "وا"].includes(head.p)
||
head.p.slice(-1) === " " // compound splits
||
head.p.match(`[${pashtoCharacterRange}]* و`)
);
}
function spaceAfterSplitHead(head: T.PsString) {
if (nuMustGoAfterSplitHead(head) && head.p.slice(-1) !== " ") {
return { p: "", f: "-" }
}
return { p: " ", f: " " };
}
function makeBasicPronounForm(
ps: T.PsString,
splitHead: T.PsString | undefined,
displayForm: T.DisplayFormForSentence,
info: T.NonComboVerbInfo,
negative: boolean,
firstPronoun: T.PsString,
secondPronoun?: T.PsString,
): T.PsString[] {
if (!negative) {
return [
ensureBaAt(
concatPsString(
firstPronoun,
" ",
secondPronoun ? concatPsString(secondPronoun, " ") : "",
ps,
),
1)
];
}
const objComplement = getObjComplement(info);
function negativeWOutSplit() {
if (!displayForm.reorderWithNegative) {
return [
ensureBaAt(
concatPsString(
firstPronoun,
" ",
secondPronoun
? concatPsString(secondPronoun, " ")
: objComplement
? concatPsString(objComplement, " ")
: "",
nuParticle,
" ",
removeAccents(objComplement ? removeObjComp(objComplement, ps) : ps)
),
1),
];
}
const [beginning, end] = splitOffLeapfrogWord(ps);
return [
ensureBaAt(
objComplement ?
concatPsString(
firstPronoun,
" ",
objComplement,
" ",
nuParticle,
" ",
end,
" ",
removeAccents(removeObjComp(objComplement, beginning)),
)
: concatPsString(
firstPronoun,
" ",
secondPronoun ? concatPsString(secondPronoun, " ") : "",
nuParticle,
" ",
end,
" ",
removeAccents(beginning),
),
1),
ensureBaAt(
concatPsString(
firstPronoun,
" ",
secondPronoun ? concatPsString(secondPronoun, " ") : "",
removeAccents(beginning),
" ",
nuParticle,
" ",
end,
),
1),
];
}
function insertNegInSplit(splitHead: T.PsString) {
if (!displayForm.reorderWithNegative) {
return [
ensureBaAt(
concatPsString(
firstPronoun,
" ",
secondPronoun ? concatPsString(secondPronoun, " ") : "",
removeAccents(splitHead),
spaceAfterSplitHead(splitHead),
nuParticle,
" ",
removeHead(splitHead, ps),
),
1),
];
}
const [beginning, end] = splitOffLeapfrogWord(ps);
return [
ensureBaAt(
concatPsString(
firstPronoun,
" ",
secondPronoun ? concatPsString(secondPronoun, " ") : "",
removeAccents(splitHead),
spaceAfterSplitHead(splitHead),
nuParticle,
" ",
end,
" ",
removeHead(splitHead, beginning),
),
1),
ensureBaAt(
concatPsString(
firstPronoun,
" ",
secondPronoun ? concatPsString(secondPronoun, " ") : "",
removeAccents(splitHead),
spaceAfterSplitHead(splitHead),
nuParticle,
" ",
removeHead(splitHead, beginning),
" ",
end,
),
1),
];
}
if (splitHead) {
return nuMustGoAfterSplitHead(splitHead) ? insertNegInSplit(splitHead) : [
...insertNegInSplit(splitHead),
...negativeWOutSplit(),
]
}
return negativeWOutSplit();
}
function makeOnlyMiniForm(
ps: T.PsString,
splitHead: T.PsString | undefined,
displayForm: T.DisplayFormForSentence,
info: T.NonComboVerbInfo,
negative: boolean,
mini: T.PsString,
): T.PsString[] {
const objComplement = getObjComplement(info);
function reorderedNegativeAfterSplitHead(splitHead: T.PsString) {
const [beginning, end] = splitOffLeapfrogWord(ps);
return ensureBaAt(
objComplement ?
concatPsString(
objComplement,
" ",
mini,
" ",
removeAccents(removeObjComp(objComplement, splitHead)),
spaceAfterSplitHead(splitHead),
nuParticle,
" ",
end,
" ",
removeHead(splitHead, beginning),
)
: concatPsString(
removeAccents(splitHead),
spaceAfterSplitHead(splitHead),
mini,
" ",
nuParticle,
" ",
end,
" ",
removeHead(splitHead, beginning),
),
1)
}
if (splitHead) {
// only mini pronoun with split
if (!displayForm.reorderWithNegative || !negative) {
const safeSplitHead = removeObjComp(objComplement, splitHead);
return [ensureBaAt(
concatPsString(
objComplement ? concatPsString(objComplement, " ", mini, " ") : "",
negative ? removeAccents(safeSplitHead) : safeSplitHead,
spaceAfterSplitHead(safeSplitHead),
!objComplement ? concatPsString(mini, " ") : "",
negative ? concatPsString(nuParticle, " ") : "",
removeHead(splitHead, ps)
),
1)];
}
// if (!nuMustGoAfterSplitHead(splitHead)) {
// TODO: IS THIS A SEPERATELY NECESSARY THING FOR VERBS LIKE
// PREXODUL -- LIKE COULD YOU ALSO DO A VERSION WHERE THE SPLIT ISN'T USED
// return [reorderedNegativeAfterSplitHead(splitHead)];
// }
return [reorderedNegativeAfterSplitHead(splitHead)];
}
// only mini without split
const [beginning, end] = splitOffLeapfrogWord(ps);
if (!displayForm.reorderWithNegative || !negative) {
if (objComplement) {
return [
concatPsString(
objComplement,
psStringContains(ps, concatPsString(baParticle, " ")) ? concatPsString(" ", baParticle, " ") : " ",
concatPsString(mini, " "),
negative ? concatPsString(" ", nuParticle, " ") : "",
removeObjComp(objComplement, psRemove(ps, concatPsString(baParticle, " "))),
)
];
}
return [
concatPsString(
psRemove(beginning, concatPsString(baParticle, " ")),
" ",
psStringContains(beginning, concatPsString(baParticle, " ")) ? concatPsString(baParticle, " ") : "",
negative ? concatPsString(" ", nuParticle, " ") : " ",
(beginning.p || negative) ? concatPsString(mini, " ") : "",
end,
(beginning.p || negative) ? "" : concatPsString(" ", mini),
),
];
}
if (objComplement) {
return [
ensureBaAt(
concatPsString(
objComplement,
" ",
mini,
" ",
nuParticle,
" ",
end,
" ",
removeObjComp(objComplement, beginning),
),
1),
ensureBaAt(
concatPsString(
objComplement,
" ",
mini,
" ",
removeObjComp(objComplement, beginning),
" ",
nuParticle,
" ",
end,
),
1),
]
}
return [
ensureBaAt(
concatPsString(
beginning,
" ",
mini,
" ",
nuParticle,
" ",
end,
),
1),
ensureBaAt(
concatPsString(
nuParticle,
" ",
end,
" ",
mini,
" ",
beginning,
),
1),
];
}
function getObjComplement(info: T.NonComboVerbInfo): T.PsString | undefined {
return info.type === "dynamic compound" ?
(info.objComplement.plural ? info.objComplement.plural : info.objComplement.entry) :
undefined;
}
function engObj(s: T.Person): string {
return (s === T.Person.FirstSingMale || s === T.Person.FirstSingFemale)
? "me"
: (s === T.Person.FirstPlurMale || s === T.Person.FirstPlurFemale)
? "us"
: (s === T.Person.SecondSingMale || s === T.Person.SecondSingFemale)
? "you"
: (s === T.Person.SecondPlurMale || s === T.Person.SecondPlurFemale)
? "you (pl.)"
: (s === T.Person.ThirdSingMale)
? "him/it"
: (s === T.Person.ThirdSingFemale)
? "her/it"
: (s === T.Person.ThirdPlurMale)
? "them"
: "them (f.)";
}

View File

@ -1,740 +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.
*
*/
// FOR ENGLISH FORMS
// GIVEN VERB INFO LIKE THIS
// ["hit", "hits", "hitting", "hit", "hit"]
// ["eat", "eats", "eating", "ate", "eaten"]
// ["see", "sees", "seeing", "saw", "seen"]
// Present Perfect
// Past Perfect
// - subj "had" (N && "not") v.4 obj
// Future Perfect
// - subj "will have" (N && "not") v.4 obj
import {
getPersonInflectionsKey,
pickPersInf,
getPersonFromVerbForm,
getVerbBlockPosFromPerson,
} from "./misc-helpers";
import addPronouns from "./add-pronouns";
import * as T from "../types";
import { englishEquative } from "./grammar-units";
type FilterFunc = (form: any) => boolean;
type MapFunc = (opts: {
subject: T.Person,
object: T.Person,
displayForm: T.DisplayFormForSentence,
info: T.NonComboVerbInfo,
negative: boolean,
englishConjugation?: T.EnglishVerbConjugation,
sentenceLevel?: "easy" | "medium" | "hard",
}) => T.DisplayFormItem;
/**
* Used to apply a filter function on both the levels of forms and subgroups
*
* @param input
* @param func
*/
const formFilter = (
input: T.DisplayFormItem[],
func: FilterFunc | FilterFunc[]
): T.DisplayFormItem[] => {
// TODO: Better filtering that lets us filter things only in sub categories
// recursive madness to apply an array of filters 🤪
// i'm doing this because I couldn't get a compose function to work 🤷‍♂️
if (Array.isArray(func)) {
if (func.length === 0) return input;
return formFilter(
formFilter(input, func[0]),
func.slice(1),
);
}
return (
input.filter(func)
.map((f) => (
"content" in f
? { ...f, content: f.content.filter(func) }
: f
))
);
};
/**
* Used to apply a filter function on both the levels of forms and subgroups
*
* @param input
* @param func
*/
const formMap = (
input: T.DisplayFormItem[],
func: MapFunc,
info: T.NonComboVerbInfo,
subject: T.Person,
object: T.Person,
negative: boolean,
englishConjugation?: T.EnglishVerbConjugation,
sentenceLevel?: "easy" | "medium" | "hard",
): T.DisplayFormItem[] => {
return input.map((f) => (
"content" in f
? { ...f, content: formMap(f.content, func, info, subject, object, negative, englishConjugation, sentenceLevel) }
: func({ displayForm: f as T.DisplayFormForSentence, info, subject, object, negative, englishConjugation, sentenceLevel })
));
};
const makeSentence = ({ subject, object, info, displayForm, englishConjugation, negative, sentenceLevel }: {
subject: T.Person,
object: T.Person,
info: T.NonComboVerbInfo,
displayForm: T.DisplayFormForSentence,
negative: boolean,
sentenceLevel?: "easy" | "medium" | "hard",
englishConjugation?: T.EnglishVerbConjugation,
}): T.DisplayForm => {
const intransitive = info.transitivity === "intransitive" || !!displayForm.passive;
const ergative = !intransitive && !!displayForm.past;
function chooseConjugation(g: T.SingleOrLengthOpts<T.VerbBlock>): T.SentenceForm {
const person = ergative
? object
: subject;
return getPersonFromVerbForm(g, person);
}
const f = displayForm.form;
// IMPORTANT TODO!!! -- IS THIS ALWAYS THE OBJECT HERE?
const matrixKey = getPersonInflectionsKey(object);
const matrixChosen = pickPersInf(f, matrixKey);
const conjugationChosen = chooseConjugation(matrixChosen);
const form = addPronouns({
s: conjugationChosen,
subject,
object,
info,
displayForm,
intransitive,
ergative,
matrixKey,
negative,
englishConjugation,
sentenceLevel,
});
return {
...displayForm,
form,
};
}
function isToBe(v: T.EnglishVerbConjugationEc): boolean {
return (v[2] === "being");
}
const futureEngBuilder: T.EnglishBuilder = (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} will${n ? " not" : ""} ${isToBe(v) ? "be" : v[0]}`,
]);
const formsOfConjugation = (conj: T.VerbConjugation): T.DisplayFormItem[] => [
{
label: "Present",
aspect: "imperfective",
form: conj.imperfective.nonImperative,
formula: "Imperfective Stem + Present Ending",
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${isToBe(v)
? `${engEquative("present", s)}${n ? " not" : ""}`
: `${n ? engPresC(s, ["don't", "doesn't"]) : ""} ${n ? v[0] : engPresC(s, v)}`}`,
`${engSubj(s)} ${engEquative("present", s)}${n ? " not" : ""} ${v[2]}`,
]),
explanation: "Something that is happening, happens generally, or is definitely about to happen. ('I am ____ing', 'I _____')",
},
{
label: "Subjunctive",
aspect: "perfective",
form: conj.perfective.nonImperative,
formula: "Perfective Stem + Present Ending",
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`that ${engSubj(s, true)}${n ? " won't" : " will"} ${isToBe(v) ? "be" : v[0]}`,
`should ${engSubj(s, true)}${n ? " not" : ""} ${isToBe(v) ? "be" : v[0]}`,
]),
explanation: "Used for hypothetical statements about the desire, necessity, purpose, or possibility of something happening. Or for saying something should or shouldn't happen. ('Should I ____?', 'so that'll I'll _____')"
},
{
label: "Imperfective Future",
aspect: "imperfective",
form: conj.imperfective.future,
advanced: true,
formula: "به - ba + Present",
sentence: true,
englishBuilder: futureEngBuilder,
explanation: "Saying something will happen, repeatedly or as an ongoing action",
},
{
label: "Perfective Future",
aspect: "perfective",
form: conj.perfective.future,
advanced: true,
formula: "به - ba + Subjunctive",
sentence: true,
englishBuilder: futureEngBuilder,
explanation: "Saying something will happen as a one-time event - May also used when there is some doubt",
},
...conj.imperfective.imperative ?
[{
label: "Imperfective Imperative",
aspect: "imperfective",
form: conj.imperfective.imperative,
formula: "Imperfective Stem + Imperative Ending",
explanation: "Commanding someone/people to do something repeatedly, or in general",
} as T.DisplayForm] : [],
...conj.perfective.imperative ?
[{
label: "Perfective Imperative",
aspect: "perfective",
form: conj.perfective.imperative,
formula: "Perfective Stem + Imperative Ending",
explanation: "Commanding someone/people to do something one time",
} as T.DisplayForm] : [],
{
label: "Continuous Past",
aspect: "imperfective",
form: conj.imperfective.past,
formula: "Imperfective Root + Past Ending",
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
// - subj pastEquative (N && "not") v.2 obj
`${engSubj(s)} ${engEquative("past", s)}${n ? " not" : ""} ${v[2]}`,
// - subj "would" (N && "not") v.0 obj
`${engSubj(s)} would${n ? " not" : ""} ${isToBe(v) ? "be" : v[0]}`,
// - subj pastEquative (N && "not") going to" v.0 obj
`${engSubj(s)} ${engEquative("past", s)}${n ? " not" : ""} going to ${isToBe(v) ? "be" : v[0]}`,
]),
explanation: "Saying something was happening, or would happen ('I was ____ing', 'I would ____')",
past: true,
},
{
label: "Simple Past",
aspect: "perfective",
form: conj.perfective.past,
formula: "Perfective Root + Past Ending",
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)}${isToBe(v)
? ` ${engEquative("past", s)}${n ? " not" : ""}`
: `${n ? " did not" : ""} ${v[3]}`}`,
]),
explanation: "Saying something happened ('I ____ed')",
past: true,
},
{
label: "Perfect",
subgroup: "perfect",
sentence: true,
content: [
{
label: "Half Perfect",
form: conj.perfect.halfPerfect,
past: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engHave(s)}${n ? " not" : ""} ${v[4]}`,
]),
formula: "Past participle inflected",
secondPronounNeeded: true,
explanation: "The base of all perfect forms. Used on it's own as a sort of abreviated form of the present perfect.",
},
{
label: "Past Perfect",
form: conj.perfect.past,
past: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} had${n ? " not" : ""} ${v[4]}`,
]),
formula: "Past participle inflected + Past Equative",
explanation: "Talking about events that had happened in the past, or had affected a past situation ('I had ____ed')",
reorderWithNegative: true,
},
{
label: "Present Perfect",
form: conj.perfect.present,
past: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engHave(s)}${n ? " not" : ""} ${v[4]}`,
]),
formula: "Past participle inflected + Present Equative",
explanation: "Talking about that something happened in the past and it affects the present ('I have _____ed')",
reorderWithNegative: true,
},
{
label: "Habitual Perfect",
form: conj.perfect.habitual,
past: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engHave(s)}${n ? " not" : ""} ${v[4]}`,
]),
formula: "Past participle inflected + Habitual Equative",
explanation: "Talking about something that will have happened habitually",
reorderWithNegative: true,
},
{
label: "Subjunctive Perfect",
form: conj.perfect.subjunctive,
past: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`that ${engSubj(s, true)} will have${n ? " not" : ""} ${v[4]}`,
]),
formula: "Past participle inflected + Subjunctive Equative",
explanation: "expressing hope, desire, or judgement about an action having happened",
reorderWithNegative: true,
},
{
label: "Future/Presumptive Perfect",
form: conj.perfect.future,
advanced: true,
past: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} will${n ? " not" : ""} have ${v[4]}`,
]),
formula: "به - ba + Past participle Inflected + Future Equative",
explanation: "Talking about something that will have happened in the future, or guessing that the event will have occured presently ('I will have ____ed')",
reorderWithNegative: true,
},
{
label: "Affirmational Perfect",
form: conj.perfect.affirmational,
advanced: true,
past: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} will${n ? " not" : ""} have ${v[4]}`,
]),
explanation: "Affirming that an event will have taken place ('I will have ____ed')",
formula: "به - ba + Past Participle Inflected + Past Equative",
reorderWithNegative: true,
},
{
label: "Conterfactual/Past Subjunctive Perfect",
form: conj.perfect.pastSubjunctiveHypothetical,
advanced: true,
past: true,
sentence: true,
secondPronounNeeded: true,
explanation: "Talking about an event that would have hypothetically taken place (but didn't), or that should have taken place but didn't",
formula: "به - ba + Past Participle Inflected + Past Subjunctive / Hypothetical Equative",
reorderWithNegative: true,
},
],
},
{
label: "Modal (ability/possibility)",
subgroup: "modal",
sentence: true,
content: [
{
label: "Present Modal",
aspect: "imperfective",
form: conj.imperfective.modal.nonImperative,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} can${n ? "'t" : ""} ${isToBe(v) ? "be" : v[0]}`,
]),
formula: "Imperfective Root + Non-Inflectinig Ey-Tail + Subjunctive کېدل - to become",
explanation: "saying that something is possible currently or in general ('I can ____')",
reorderWithNegative: true,
},
{
label: "Subjunctive Modal",
aspect: "perfective",
form: conj.perfective.modal.nonImperative,
advanced: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`that ${engSubj(s, true)} can${n ? "'t" : ""} ${isToBe(v) ? "be" : v[0]}`,
]),
formula: "Perfective Root + Non-Inflectinig Ey-Tail + Subjunctive کېدل - to become",
explanation: "talking about the possibility of something in a subjunctive way ('so that I can ____')",
reorderWithNegative: true,
},
{
label: "Imperfective Future Modal",
aspect: "imperfective",
form: conj.imperfective.modal.future,
advanced: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} will${n ? " not" : ""} be able to ${isToBe(v) ? "be" : v[0]}`,
]),
formula: "به - ba + Present Modal",
explanation: "saying that something will be possible in general or in an ongoing sense in the future ('I'll be able to ____')",
reorderWithNegative: true,
},
{
label: "Perfective Future Modal",
aspect: "perfective",
form: conj.perfective.modal.future,
advanced: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} will${n ? " not" : ""} be able to ${isToBe(v) ? "be" : v[0]}`,
]),
formula: "به - ba + Subjunctive Modal",
explanation: "saying that something will be possible at a certain point in the future ('I'll be able to ____')",
reorderWithNegative: true,
},
{
label: "Continous Past Modal",
aspect: "imperfective",
form: conj.imperfective.modal.past,
advanced: true,
past: true,
sentence: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engEquative("past", s)} ${n ? " not" : ""} able to ${isToBe(v) ? "be" : v[0]}`,
`${engSubj(s)} could${n ? " not" : ""} ${v[0]}`,
]),
formula: "Imperfective Root + Non-Inflectinig Ey-Tail + Simple Past کېدل - to become",
explanation: "saying that something was possible in general, in an ongoing sense ('I was able to ____', ie. 'I could do ____ any time')",
reorderWithNegative: true,
},
{
label: "Simple Past Modal",
aspect: "perfective",
form: conj.perfective.modal.past,
formula: "Perfective Root + Non-Inflectinig Ey-Tail + Simple Past کېدل - to become",
explanation: "saying that something was possible at a certain point in time ('I was able to ____, at one particular point in time')",
past: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engEquative("past", s)} ${n ? " not" : ""} able to ${isToBe(v) ? "be" : v[0]}`,
`${engSubj(s)} could${n ? " not" : ""} ${isToBe(v) ? "be" : v[0]}`,
]),
sentence: true,
advanced: true,
reorderWithNegative: true,
},
{
label: "Imperfective hypothetical/wildcard Past Modal",
aspect: "imperfective",
form: conj.imperfective.modal.hypotheticalPast,
formula: "Imperfective Root + Non-Inflectinig Ey-Tail + ش - sh + Non-Inflectinig Ey-Tail",
explanation: "saying that something was possible in general, in an ongoing sense ('I was able to ____', ie. 'I could do ____ any time'). This 'wildcard' form can be used either to talk about hypothetical things, or to avoid worrying about verb agreement",
past: true,
sentence: true,
advanced: true,
reorderWithNegative: true,
},
{
label: "Perfective hypothetical/wildcard Past Modal",
aspect: "perfective",
form: conj.perfective.modal.hypotheticalPast,
formula: "Perfective Root + Non-Inflectinig Ey-Tail + ش - sh + Non-Inflectinig Ey-Tail",
explanation: "saying that something was possible at a certain point in time ('I was able to ____, at one particular point in time'). This 'wildcard' form can be used either to talk about hypothetical things, or to avoid worrying about verb agreement",
past: true,
sentence: true,
advanced: true,
reorderWithNegative: true,
},
],
},
{
label: "Hypothetical/Wish",
advanced: true,
form: conj.hypothetical,
formula: "Imperfective Root + Non-Inflecting Ey-Tail",
explanation: "Talking about a hypothetical, unreal situation, or something that is wished for ('If I ____')",
past: true,
},
{
label: "Participle",
subgroup: "participle",
advanced: true,
content: [
{
label: "Present Participle",
form: conj.participle.present,
formula: "Short form of Ininitive Root + ونکی - oonkey",
explanation: "Making a verb into a noun or adjective, talking about a person or thing that does or experiences something. Also used to say something is about to happen. ('____ing', '____er')",
},
{
label: "Past Participle",
form: conj.participle.past,
past: true,
formula: "Infinitive Root or Special Form + Inflecting Ey-Tail",
explanation: "Making a verb into a noun or adjective, talking about how a person or thing did or experienced something. ('____ed')",
},
],
},
...conj.passive ?
[{
label: "Passive",
subgroup: "passive",
advanced: true,
sentence: true,
content: [
{
label: "Passive Present",
aspect: "imperfective",
form: conj.passive.imperfective.nonImperative,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engEquative("present", s)}${n ? " not" : ""} being ${v[4]}`,
]),
formula: "Long Imperfective Root + Present کېدل - to become",
explanation: "Saying that something is being done or is done in general, without mentioning the subject/agent. ('I am being ____en')",
},
{
label: "Passive Subjunctive",
aspect: "perfective",
form: conj.passive.perfective.nonImperative,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`that ${engSubj(s, true)} will${n ? " not" : ""} be ${v[4]}`,
]),
formula: "Long Perfective Root + Subjunctive کېدل - to become",
explanation: "Saying that something should be done, or giving a purpose for something being done etc., without mentioning the subject/agent. ('Should I be ____en?', 'So that I'll be ____en')"
},
{
label: "Passive Imperfective Future",
aspect: "imperfective",
form: conj.passive.imperfective.future,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} will${n ? " not" : ""} be ${v[4]}`,
]),
formula: "به - ba + Passive Present",
explanation: "Saying something will be done as a one-time event, without mentioning the subject/agent.",
},
{
label: "Passive Perfective Future",
aspect: "perfective",
form: conj.passive.perfective.future,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} will${n ? " not" : ""} be ${v[4]}`,
]),
formula: "به - ba + Passive Subjunctive",
explanation: "Saying something will be done in an ongoing or repeated sense, without mentioning the subject/agent."
},
{
label: "Passive Continuous Past",
aspect: "imperfective",
form: conj.passive.imperfective.past,
past: true,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engEquative("past", s)}${n ? " not" : ""} being ${v[4]}`,
]),
formula: "Long Imperfective Root + Continuous Past کېدل - to become",
explanation: "Saying that something was being done, or would be done, without mentioning the subject/agent. ('I was being ____en', 'I would be ____en')",
},
{
label: "Passive Simple Past",
aspect: "perfective",
form: conj.passive.perfective.past,
past: true,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engEquative("past", s)}${n ? " not" : ""} ${v[4]}`,
]),
formula: "Long Perfective Root + Simple Past کېدل - to become",
explanation: "Saying that was done as a one-time event, without mentioning the subject/agent. ('I was ____en')"
},
{
label: "Passive Perfect",
subgroup: "passive perfect",
passive: true,
sentence: true,
content: [
{
label: "Passive Half Perfect",
form: conj.passive.perfect.halfPerfect,
past: true,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engHave(s)}${n ? " not" : ""} been ${v[4]}`,
]),
formula: "Infinitive + کېدل past participle inflected",
explanation: "The base of all perfect forms. Used on it's own as a sort of abbreviated form of the present perfect. (Passive voice)",
},
{
label: "Passive Past Perfect",
form: conj.passive.perfect.past,
past: true,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} had${n ? " not" : ""} been ${v[4]}`,
]),
formula: "Infinitive + کېدل past participle inflected + Past Equative",
explanation: "Talking about events that had happened in the past, or had affected a past situation (Passive voice) ('I had been ____ed')",
},
{
label: "Passive Present Perfect",
form: conj.passive.perfect.present,
past: true,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engHave(s)}${n ? " not" : ""} been ${v[4]}`,
]),
formula: "Infinitive + کېدل past participle inflected + Present Equative",
explanation: "Talking about that something happened in the past and it affects the present (Passive voice) ('I have been _____ed')",
},
{
label: "Passive Habitual Perfect",
form: conj.passive.perfect.subjunctive,
past: true,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} ${engHave(s)}${n ? " not" : ""} been ${v[4]}`,
]),
formula: "Infinitive + کېدل past participle inflected + Habitual Equative",
},
{
label: "Passive Subjunctive Perfect",
form: conj.passive.perfect.subjunctive,
past: true,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`that ${engSubj(s, true)} will${n ? " not" : ""} have been ${v[4]}`,
]),
formula: "Infinitive + کېدل past participle inflected + Subjunctive Equative",
},
{
label: "Passive Future/Presumptive Perfect",
form: conj.passive.perfect.future,
advanced: true,
past: true,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} will${n ? " not" : ""} have been ${v[4]}`,
]),
formula: "به - ba + Infinitive + کېدل past participle inflected + Future Equative",
explanation: "Talking about something that will have happened in the future, or guessing that the event will have occured presently (Passive voice) ('I will have been ____ed')",
},
{
label: "Passive Affirmational Perfect",
form: conj.passive.perfect.affirmational,
advanced: true,
past: true,
sentence: true,
passive: true,
englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
`${engSubj(s)} will${n ? " not" : ""} have been ${v[4]}`,
]),
explanation: "Affirming that an event will have taken place (Passive voice) ('I will have been ____ed')",
formula: "به - ba + Infinitive + کېدل past participle inflected + Past Equative"
},
{
label: "Passive Past Subjunctive / Hypothetical Perfect",
form: conj.passive.perfect.pastSubjunctiveHypothetical,
advanced: true,
past: true,
sentence: true,
passive: true,
explanation: "Talking about an event that would have hypothetically taken place, or that should have taken place (Passive voice) ('I would have been ____ed')",
formula: "به - ba + Infinitive + کېدل past participle inflected + Past Subjunctive / Hypothetical Equative"
},
],
},
]
} as T.DisplayFormSubgroup]
: [],
];
export const getForms = ({ conj, filterFunc, mode, subject, object, sentenceLevel, englishConjugation, negative }: {
conj: T.VerbConjugation,
englishConjugation?: T.EnglishVerbConjugation
filterFunc?: FilterFunc | FilterFunc[],
mode: "chart" | "sentence",
subject: T.Person,
object: T.Person,
sentenceLevel?: "easy" | "medium" | "hard",
negative: boolean,
}): T.DisplayFormItem[] => {
const forms = formsOfConjugation(conj);
const formsToUse = filterFunc
? formFilter(forms, filterFunc)
: forms;
return mode === "chart"
? formsToUse
: formMap(
formFilter(formsToUse, (f) => f.sentence),
makeSentence,
conj.info,
subject,
object,
negative,
englishConjugation,
sentenceLevel,
);
}
function isThirdPersonSing(p: T.Person): boolean {
return (
p === T.Person.ThirdSingMale ||
p === T.Person.ThirdSingFemale
);
}
function engPresC(s: T.Person, ec: T.EnglishVerbConjugationEc | [string, string]): string {
return isThirdPersonSing(s) ? ec[1] : ec[0];
}
function engEquative(tense: "past" | "present", s: T.Person): string {
const [row, col] = getVerbBlockPosFromPerson(s);
return englishEquative[tense][row][col];
}
function engHave(s: T.Person): string {
return isThirdPersonSing(s) ? "has" : "have";
}
function engSubj(s: T.Person, lowerCase?: boolean): string {
const pronoun = (s === T.Person.FirstSingMale || s === T.Person.FirstSingFemale)
? "I"
: (s === T.Person.FirstPlurMale || s === T.Person.FirstPlurFemale)
? "We"
: (s === T.Person.SecondSingMale || s === T.Person.SecondSingFemale)
? "You"
: (s === T.Person.SecondPlurMale || s === T.Person.SecondPlurFemale)
? "You (pl.)"
: (s === T.Person.ThirdSingMale)
? "He/it"
: (s === T.Person.ThirdSingFemale)
? "She/it"
: (s === T.Person.ThirdPlurMale)
? "They"
: "They (f.)";
return (lowerCase && pronoun !== "I")
? pronoun.toLowerCase()
: pronoun;
}

View File

@ -9,6 +9,14 @@
import { kawulStat } from "./irregular-conjugations";
import * as T from "../types";
export const negativeParticle: {
imperative: T.PsString,
nonImperative: T.PsString,
} = {
nonImperative: { p: "نه", f: "nú" },
imperative: { p: "مه", f: "mú" },
};
export const presentEndings: T.VerbBlock = [
[
[{

View File

@ -687,6 +687,21 @@ export function uniquePsStringArray(arr: T.PsString[]): T.PsString[] {
].map((string) => JSON.parse(string)) as T.PsString[];
}
export function splitOffLeapfrogWordFull(ps: T.SingleOrLengthOpts<T.PsString[]>): [T.SingleOrLengthOpts<T.PsString[]>, T.SingleOrLengthOpts<T.PsString[]>] {
if ("long" in ps) {
const [shortA, shortB] = splitOffLeapfrogWordFull(ps.short) as [T.PsString[], T.PsString[]];
const [longA, longB] = splitOffLeapfrogWordFull(ps.long) as [T.PsString[], T.PsString[]];
return [{ long: longA, short: shortA }, { long: longB, short: shortB }];
}
return ps.reduce((accum, curr): [T.PsString[], T.PsString[]] => {
const [front, back] = splitOffLeapfrogWord(curr);
return [
[...accum[0], front],
[...accum[1], back],
];
}, [[], []] as [T.PsString[], T.PsString[]])
}
export function splitOffLeapfrogWord(ps: T.PsString): [T.PsString, T.PsString] {
const pWords = ps.p.split(" ");
const fWords = ps.f.split(" ");
@ -984,8 +999,12 @@ export function psStringFromEntry(entry: T.PsString): T.PsString {
};
}
export function getLength<U>(x: T.SingleOrLengthOpts<U>, length: "long" | "short"): U {
return ("long" in x) ? x[length] : x;
export function getLength<U>(x: T.SingleOrLengthOpts<U>, length: "long" | "short" | "mini"): U {
if ("long" in x) {
const s = x[length];
return s ? s : x.short;
}
return x;
}
export function getLong<U>(x: T.SingleOrLengthOpts<U>): U {

View File

@ -11,24 +11,61 @@ export function getSubjectSelection(blocks: T.EPSBlock[] | T.EPSBlockComplete[]
return b.block;
}
export function getSubjectSelectionFromBlocks(blocks: T.Block[]): T.Rendered<T.SubjectSelectionComplete> {
const b = blocks.find(f => f.type === "subjectSelection");
export function getSubjectSelectionFromBlocks(blocks: T.Block[][]): T.Rendered<T.SubjectSelectionComplete> {
const b = blocks[0].find(f => f.type === "subjectSelection");
if (!b || b.type !== "subjectSelection") {
throw new Error("subjectSelection not found in blocks");
}
return b;
}
export function getPredicateSelectionFromBlocks(blocks: T.Block[]): T.Rendered<T.PredicateSelectionComplete> {
const b = blocks.find(f => f.type === "predicateSelection");
export function getObjectSelectionFromBlocks(blocks: T.Block[][]): T.Rendered<T.ObjectSelectionComplete> {
const b = blocks[0].find(f => f.type === "objectSelection");
if (!b || b.type !== "objectSelection") {
throw new Error("objectSelection not found in blocks");
}
return b;
}
export function getVerbFromBlocks(blocks: T.Block[][]): T.VerbRenderedBlock {
const b = blocks[0].find(f => f.type === "verb");
const p = blocks[0].find(f => f.type === "perfectParticipleBlock");
const m = blocks[0].find(f => f.type === "modalVerbBlock");
const v = (b && b.type === "verb")
? b
: (p && p.type === "perfectParticipleBlock")
? p.verb
: (m && m.type === "modalVerbBlock")
? m.verb
: undefined;
if (!v) {
throw new Error("verbSelection not found in blocks");
}
return v;
}
export function getVerbAndHeadFromBlocks(blocks: T.Block[][]): { verb: T.VerbRenderedBlock, perfectiveHead: T.PerfectiveHeadBlock } {
const verb = getVerbFromBlocks(blocks);
const perfectiveHead = blocks[0].find(f => f.type === "perfectiveHead");
if (!perfectiveHead || perfectiveHead.type !== "perfectiveHead") {
throw new Error("perfectiveHead not found in blocks");
}
return {
verb,
perfectiveHead,
};
}
export function getPredicateSelectionFromBlocks(blocks: T.Block[][]): T.Rendered<T.PredicateSelectionComplete> {
const b = blocks[0].find(f => f.type === "predicateSelection");
if (!b || b.type !== "predicateSelection") {
throw new Error("predicateSelection not found in blocks");
}
return b;
}
export function getAPsFromBlocks(blocks: T.Block[]): T.Rendered<T.APSelection>[] {
return blocks.filter(b => b.type === "AP") as T.Rendered<T.APSelection>[];
export function getAPsFromBlocks(blocks: T.Block[][]): T.Rendered<T.APSelection>[] {
return blocks[0].filter(b => b.type === "AP") as T.Rendered<T.APSelection>[];
}
export function getObjectSelection(blocks: T.VPSBlockComplete[]): T.ObjectSelectionComplete;
@ -41,23 +78,6 @@ export function getObjectSelection(blocks: T.VPSBlock[] | T.VPSBlockComplete[]):
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 [
{
@ -206,29 +226,77 @@ export function isNoObject(b: T.VPSBlock["block"] | T.EPSBlock["block"]): b is {
return !!(b && b.type === "objectSelection" && b.selection === "none");
}
export function specifyEquativeLength(blocks: T.Block[], length: "long" | "short"): T.Block[] {
const i = blocks.findIndex(b => b.type === "equative");
if (i === -1) throw new Error("equative block not found in EPRendered");
const eq = blocks[i];
if (eq.type !== "equative") throw new Error("error searching for equative block");
const adjusted = [...blocks];
adjusted[i] = {
...eq,
equative: {
...eq.equative,
ps: getLength(eq.equative.ps, length),
},
};
return adjusted;
export function specifyEquativeLength(blocksWVars: T.Block[][], length: "long" | "short"): T.Block[][] {
function specify(blocks: T.Block[]): T.Block[] {
const i = blocks.findIndex(b => b.type === "equative");
if (i === -1) throw new Error("equative block not found in EPRendered");
const eq = blocks[i];
if (eq.type !== "equative") throw new Error("error searching for equative block");
const adjusted = [...blocks];
adjusted[i] = {
...eq,
equative: {
...eq.equative,
ps: getLength(eq.equative.ps, length),
},
};
return adjusted;
}
return blocksWVars.map(specify);
}
export function hasEquativeWithLengths(blocks: T.Block[]): boolean {
const equative = blocks.find(x => x.type === "equative");
export function specifyVerbLength(blocksWVars: T.Block[][], length: "long" | "short" | "mini"): T.Block[][] {
function specify(blocks: T.Block[]): T.Block[] {
return blocks.map((block) => {
if (block.type === "verb") {
const v: T.VerbRenderedBlock = {
...block,
block: {
...block.block,
ps: getLength(block.block.ps, length),
},
};
return v;
}
if (block.type === "perfectParticipleBlock") {
const p: T.PerfectParticipleBlock = {
...block,
ps: getLength(block.ps, length),
};
return p;
}
if (block.type === "modalVerbBlock") {
const m: T.ModalVerbBlock = {
...block,
ps: getLength(block.ps, length),
};
return m;
}
return block;
});
}
return blocksWVars.map(specify);
}
export function hasEquativeWithLengths(blocks: T.Block[][]): boolean {
const equative = blocks[0].find(x => x.type === "equative");
if (!equative) throw new Error("equative not found in blocks");
if (equative.type !== "equative") throw new Error("error finding equative in blocks");
return "long" in equative.equative.ps;
}
export function hasVerbWithLengths(blocks: T.Block[][]): boolean {
// TODO: handle length options with perfect verb equative as well?
const verb = blocks[0].find(x => (x.type === "verb" || x.type === "perfectParticipleBlock" || x.type === "modalVerbBlock"));
if (!verb) throw new Error("verb not found in blocks");
if (verb.type !== "verb" && verb.type !== "perfectParticipleBlock" && verb.type !== "modalVerbBlock") throw new Error("error finding verb in blocks");
return (
(verb.type === "verb" && "long" in verb.block.ps)
|| (verb.type === "perfectParticipleBlock" && "long" in verb.ps)
|| (verb.type === "modalVerbBlock" && "long" in verb.ps)
);
}
function arrayMove<X>(ar: X[], old_index: number, new_index: number): X[] {
const arr = [...ar];
const new_i = (new_index >= arr.length)

View File

@ -1,33 +1,27 @@
import * as T from "../../types";
import {
concatPsString, getLong,
concatPsString, getLong, getShort,
} from "../p-text-helpers";
import {
Segment,
makeSegment,
flattenLengths,
combineSegments,
splitOffLeapfrogWord,
putKidsInKidsSection as oldPutKidsInKidsSection,
} from "./segment";
import {
removeAccents,
} from "../accent-helpers";
import { negativeParticle } from "../../lib/grammar-units";
import * as grammarUnits from "../grammar-units";
import {
removeBa,
removeDuplicates,
} from "./vp-tools";
import { isImperativeTense, isModalTense, isPerfectTense } from "../type-predicates";
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
import { getVerbBlockPosFromPerson } from "../misc-helpers";
import { pronouns } from "../grammar-units";
import { completeEPSelection, renderEP } from "./render-ep";
import { completeVPSelection } from "./vp-tools";
import { renderVP } from "./render-vp";
import { getAPsFromBlocks, getPredicateSelectionFromBlocks, getRenderedObjectSelection, getRenderedSubjectSelection, getSubjectSelectionFromBlocks, hasEquativeWithLengths, specifyEquativeLength } from "./blocks-utils";
// TODO: GET BLANKING WORKING!
import {
getAPsFromBlocks,
getObjectSelectionFromBlocks,
getPredicateSelectionFromBlocks,
getSubjectSelectionFromBlocks,
getVerbFromBlocks,
hasEquativeWithLengths,
hasVerbWithLengths,
specifyEquativeLength,
specifyVerbLength,
} from "./blocks-utils";
const blank: T.PsString = {
p: "______",
@ -37,228 +31,31 @@ type BlankoutOptions = { equative?: boolean, ba?: boolean, kidsSection?: boolean
const kidsBlank: T.PsString = { p: "___", f: "___" };
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, blocks } = getVPSegmentsAndKids(VP, form);
const psResult = compilePs({
blocks,
kids,
verb,
VP,
});
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 : compileEnglishVP(VP),
};
}
type CompilePsInput = {
blocks: Segment[],
kids: Segment[],
verb: {
head: T.PsString | undefined,
rest: T.SingleOrLengthOpts<T.PsString[]>,
},
VP: T.VPRendered,
}
// function compilePs({ blocks, kids, verb: { head, rest }, VP }: CompilePsInput): T.SingleOrLengthOpts<T.PsString[]> {
// if ("long" in rest) {
// return {
// 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({ blocks, verb: { head, rest: rest.mini }, VP, kids }) as T.PsString[],
// } : {},
// };
// }
// const verbWNegativeVersions = arrangeVerbWNegative(head, rest, VP.verb);
function compilePs({ blocks, kids, verb: { head, rest }, VP }: CompilePsInput): T.SingleOrLengthOpts<T.PsString[]> {
if ("long" in rest) {
return {
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({ blocks, verb: { head, rest: rest.mini }, VP, kids }) as T.PsString[],
} : {},
};
}
const verbWNegativeVersions = arrangeVerbWNegative(head, rest, VP.verb);
// put together all the different possible permutations based on:
// a. potential different versions of where the nu goes
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 = oldPutKidsInKidsSection([...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: T.Rendered<T.NPSelection> | undefined = (() => {
if (!shrinkServant) return undefined;
if (!VP.servant) return undefined;
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?: 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 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.selection.person)),
]
}
return [
...accum,
makeSegment(getPashtoFromRendered(block, false)),
];
}, [] as Segment[]);
return {
kids: orderKidsSection([
...VP.verb.hasBa
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] : [],
...shrunkenServant
? [shrunkenServant] : [],
...possToShrink.map(shrinkNP),
]),
blocks: blocks,
};
}
function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[], V: T.VerbRendered): Segment[][] {
const hasLeapfrog = isPerfectTense(V.tense) || isModalTense(V.tense);
const rest = (() => {
if (hasLeapfrog) {
const [restF, restLast] = splitOffLeapfrogWord(restRaw);
return {
front: makeSegment(restF.map(removeBa), ["isVerbRest"]),
last: makeSegment(restLast.map(removeBa), ["isVerbRest"]),
};
}
return makeSegment(restRaw.map(removeBa), ["isVerbRest"]);
})();
const headSegment: Segment | undefined = !head
? head
: makeSegment(
head,
(head.p === "و" || head.p === "وا")
? ["isVerbHead", "isOoOrWaaHead"]
: ["isVerbHead"]
);
if (!V.negative) {
if ("front" in rest) {
return [
headSegment ? [headSegment, rest.front, rest.last] : [rest.front, rest.last],
]
}
return [
headSegment ? [headSegment, rest] : [rest],
];
}
const nu: T.PsString = isImperativeTense(V.tense)
? { p: "مه", f: "mú" }
: { p: "نه", f: "nú" };
if (!headSegment) {
if ("front" in rest) {
return [
// pefect nu dey me leeduley and nu me dey leeduley
// actually don't think this is correct - keeping it out for now
// [
// mergeSegments(
// makeSegment(nu, ["isNu"]),
// rest.last.adjust({ ps: removeAccents }),
// ),
// rest.front.adjust({ ps: removeAccents }),
// ],
[
makeSegment(nu, ["isNu"]),
rest.last.adjust({ ps: removeAccents }),
rest.front.adjust({ ps: removeAccents }),
],
[
rest.front.adjust({ ps: removeAccents }),
makeSegment(nu, ["isNu"]),
rest.last.adjust({ ps: removeAccents }),
],
];
}
return [[
makeSegment(nu, ["isNu"]),
rest.adjust({ ps: removeAccents }),
]];
}
if ("front" in rest) {
return [
[
headSegment.adjust({ ps: removeAccents }),
rest.last.adjust({
ps: r => concatPsString(nu, " ", removeAccents(r)),
desc: ["isNu"],
}),
rest.front.adjust({
ps: r => removeAccents(r),
}),
],
[
headSegment.adjust({ ps: removeAccents }),
rest.front.adjust({
ps: r => concatPsString(nu, " ", removeAccents(r)),
desc: ["isNu"],
}),
rest.last.adjust({
ps: r => removeAccents(r),
}),
],
...(!headSegment.isOoOrWaaHead && !V.isCompound) ? [[
mergeSegments(headSegment, rest.front, "no space").adjust({
ps: r => concatPsString(nu, " ", removeAccents(r)),
desc: ["isNu"],
}),
rest.last.adjust({
ps: r => removeAccents(r),
}),
]] : [],
];
}
return [
...(V.voice !== "passive") ? [[
...headSegment ? [headSegment.adjust({ ps: removeAccents })] : [],
rest.adjust({
ps: r => concatPsString(nu, " ", removeAccents(r)),
desc: ["isNu"],
}),
]] : [],
// verbs that have a perfective prefix that is not و or وا can put the
// nu *before* the prefix as well // TODO: also وي prefixes?
...((!headSegment.isOoOrWaaHead && !V.isCompound) || (V.voice === "passive")) ? [[
makeSegment(nu, ["isNu"]),
headSegment.adjust({ ps: removeAccents }),
rest.adjust({ ps: removeAccents }),
]] : [],
];
}
// // put together all the different possible permutations based on:
// // a. potential different versions of where the nu goes
// 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 = oldPutKidsInKidsSection([...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 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[] };
@ -270,7 +67,36 @@ export function compileEP(EP: T.EPRendered, combineLengths?: boolean, blankOut?:
};
}
function compileEPPs(blocks: T.Block[], kids: T.Kid[], omitSubject: boolean, blankOut?: BlankoutOptions): T.SingleOrLengthOpts<T.PsString[]> {
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 = getVerbFromBlocks(VP.blocks).block;
const psResult = compileVPPs(VP.blocks, VP.kids, form, VP.king);
return {
ps: combineLengths ? flattenLengths(psResult) : psResult,
// TODO: English doesn't quite work for dynamic compounds in passive voice
e: (verb.voice === "passive" && VP.isCompound === "dynamic") ? undefined : compileEnglishVP(VP),
};
}
function compileVPPs(blocks: T.Block[][], kids: T.Kid[], form: T.FormVersion, king: "subject" | "object"): T.SingleOrLengthOpts<T.PsString[]> {
if (hasVerbWithLengths(blocks)) {
return {
long: compileVPPs(specifyVerbLength(blocks, "long"), kids, form, king) as T.PsString[],
short: compileVPPs(specifyVerbLength(blocks, "short"), kids, form, king) as T.PsString[],
};
}
const subjectPerson = getSubjectSelectionFromBlocks(blocks)
.selection.selection.person;
const blocksWKids = putKidsInKidsSection(
filterForVisibleBlocksVP(blocks, form, king),
kids,
false,
);
return removeDuplicates(combineIntoText(blocksWKids, subjectPerson, {}));
}
function compileEPPs(blocks: T.Block[][], kids: T.Kid[], omitSubject: boolean, blankOut?: BlankoutOptions): T.SingleOrLengthOpts<T.PsString[]> {
if (hasEquativeWithLengths(blocks)) {
return {
long: compileEPPs(specifyEquativeLength(blocks, "long"), kids, omitSubject, blankOut) as T.PsString[],
@ -280,31 +106,74 @@ function compileEPPs(blocks: T.Block[], kids: T.Kid[], omitSubject: boolean, bla
const subjectPerson = getSubjectSelectionFromBlocks(blocks)
.selection.selection.person;
const blocksWKids = putKidsInKidsSection(
omitSubject ? blocks.filter(b => b.type !== "subjectSelection") : blocks,
omitSubject ? blocks.map(blks => blks.filter(b => b.type !== "subjectSelection")) : blocks,
kids,
!!blankOut?.kidsSection
);
// BIG TODO: If the kid's section is blank and there are no kids - add a blank for the kids section!
return removeDuplicates(combineIntoText(blocksWKids, subjectPerson, blankOut));
}
function combineIntoText(pieces: (T.Block | T.Kid | T.PsString)[], subjectPerson: T.Person, blankOut?: BlankoutOptions): T.PsString[] {
const first = pieces[0];
const rest = pieces.slice(1);
const firstPs = ("p" in first)
? [first]
: (blankOut?.equative && first.type === "equative")
? [blank]
: ((blankOut?.ba) && first.type === "ba")
? [kidsBlank]
: getPsFromPiece(first, subjectPerson);
if (!rest.length) {
return firstPs;
export function filterForVisibleBlocksVP(blocks: T.Block[][], form: T.FormVersion, king: "subject" | "object"): T.Block[][] {
const servant = king === "object" ? "subject" : "object";
return blocks.map(blks => blks.filter((block) => {
if (form.removeKing) {
if (
(king === "subject" && block.type === "subjectSelection")
||
(king === "object" && block.type === "objectSelection")
) return false;
}
if (form.shrinkServant) {
if (
(servant === "subject" && block.type === "subjectSelection")
||
(servant === "object" && block.type === "objectSelection")
) return false;
}
if (block.type === "objectSelection" && typeof block.selection !== "object") {
return false;
}
return true;
}));
}
export function filterForVisibleBlocksEP(blocks: T.Block[][], omitSubject: boolean): T.Block[][] {
if (!omitSubject) return blocks;
return blocks.map(blks => blks.filter((block) => {
if (block.type === "subjectSelection") {
return false;
}
return true;
}));
}
function combineIntoText(piecesWVars: (T.Block | T.Kid | T.PsString)[][], subjectPerson: T.Person, blankOut?: BlankoutOptions): T.PsString[] {
function combine(pieces: (T.Block | T.Kid | T.PsString)[]): T.PsString[] {
const first = pieces[0];
const next = pieces[1];
const rest = pieces.slice(1);
const firstPs = ("p" in first)
? [first]
: (blankOut?.equative && first.type === "equative")
? [blank]
: ((blankOut?.ba) && first.type === "ba")
? [kidsBlank]
: getPsFromPiece(first, subjectPerson);
if (!rest.length) {
return firstPs;
}
return combine(rest).flatMap(r => (
firstPs.map(fPs => concatPsString(
fPs,
(!("p" in first) && first.type === "perfectiveHead" && !("p" in next) && (next.type === "verb" || next.type === "negative" || next.type === "mini-pronoun"))
? ((next.type === "negative" || next.type === "mini-pronoun") ? { p: "", f: "-" } : "")
: " ",
r,
))
)
);
}
return combineIntoText(rest, subjectPerson, blankOut).flatMap(r => (
firstPs.map(fPs => concatPsString(fPs, " ", r))
)
);
return piecesWVars.flatMap(combine);
}
function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsString[] {
@ -314,8 +183,10 @@ function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsSt
if (piece.type === "mini-pronoun") {
return [piece.ps];
}
if (piece.type === "nu") {
return [{ p: "نه", f: "nú" }];
if (piece.type === "negative") {
return [
negativeParticle[piece.imperative ? "imperative" : "nonImperative"],
];
}
if (piece.type === "equative") {
// length will already be specified in compileEPPs - this is just for type safety
@ -324,12 +195,45 @@ function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsSt
if (piece.type === "subjectSelection" || piece.type === "predicateSelection") {
return getPashtoFromRendered(piece.selection, subjectPerson);
}
// if (piece.type === "AP") {
if (piece.type === "AP") {
return getPashtoFromRendered(piece, subjectPerson);
// }
}
if (piece.type === "perfectiveHead") {
return [piece.ps];
}
if (piece.type === "verbComplement") {
return [{ p: "---", f: "---"}]; //getPashtoFromRendered(piece.complement);
}
if (piece.type === "objectSelection") {
if (typeof piece.selection !== "object") {
return [{ p: "", f: "" }];
}
return getPashtoFromRendered(piece.selection, subjectPerson);
}
if (piece.type === "verb") {
// getLong is just for type safety - we will have split up the length options earlier in compileVPPs
return getLong(piece.block.ps);
}
if (piece.type === "perfectParticipleBlock") {
// getLong is just for type safety - we will have split up the length options earlier in compileVPPs
return getLong(piece.ps);
}
if (piece.type === "perfectEquativeBlock") {
// just using the short one for now - it will only be short anyways
return getShort(piece.ps);
}
if (piece.type === "modalVerbBlock") {
// getLong is just for type safety - we will have split up the length options earlier in compileVPPs
return getLong(piece.ps);
}
if (piece.type === "modalVerbKedulPart") {
// just using the short one for now - it will only be short anyways
return getShort(piece.ps);
}
throw new Error("unrecognized piece type");
}
function getEngAPs(blocks: T.Block[]): string {
function getEngAPs(blocks: T.Block[][]): string {
return getAPsFromBlocks(blocks).reduce((accum, curr) => {
const e = getEnglishFromRendered(curr);
if (!e) return accum;
@ -337,76 +241,31 @@ function getEngAPs(blocks: T.Block[]): string {
}, "");
}
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);
if (!e) return accum;
return `${accum} ${e}`;
}, "");
}
function putKidsInKidsSection(blocks: T.Block[], kids: T.Kid[], enforceKidsSectionBlankout: boolean): (T.Block | T.Kid | T.PsString)[] {
const first = blocks[0];
const rest = blocks.slice(1);
return [
first,
...enforceKidsSectionBlankout ? [kidsBlank] : kids,
...rest,
];
}
function mergeSegments(s1: Segment, s2: Segment, noSpace?: "no space"): Segment {
if (noSpace) {
return s2.adjust({ ps: (p) => concatPsString(s1.ps[0], p) });
function putKidsInKidsSection(blocksWVars: T.Block[][], kids: T.Kid[], enforceKidsSectionBlankout: boolean): (T.Block | T.Kid | T.PsString)[][] {
function insert(blocks: T.Block[]): (T.Block | T.Kid | T.PsString)[] {
const first = blocks[0];
const rest = blocks.slice(1);
return [
first,
...enforceKidsSectionBlankout ? [kidsBlank] : kids,
...rest,
];
}
return s2.adjust({ ps: (p) => concatPsString(s1.ps[0], " ", p) });
}
function addSpacesBetweenSegments(segments: Segment[]): (Segment | " " | "" | T.PsString)[] {
const o: (Segment | " " | "" | T.PsString)[] = [];
for (let i = 0; i < segments.length; i++) {
const current = segments[i];
const next = segments[i+1];
o.push(current);
if (!next) break;
if (
// stative compound part
!current.ps[0].p.endsWith(" ")
&&
(
(next.isKidBetweenHeadAndRest || next.isNu)
||
(next.isVerbRest && current.isKidBetweenHeadAndRest)
)
) {
o.push({
f: " ", // TODO: make this "-" in the right places
p: ((current.isVerbHead && (next.isMiniPronoun || next.isNu))
|| (current.isOoOrWaaHead && (next.isBa || next.isNu))) ? "" : " ", // or if its waa head
});
} else if (current.isVerbHead && next.isVerbRest) {
o.push("");
} else {
o.push(" ");
}
}
return o;
return blocksWVars.map(insert);
}
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 = getRenderedSubjectSelection(VP.blocks).selection;
const obj = getRenderedObjectSelection(VP.blocks).selection;
const engSubj = getSubjectSelectionFromBlocks(VP.blocks).selection;
const obj = getObjectSelectionFromBlocks(VP.blocks).selection;
const engObj = typeof obj === "object"
? obj
: obj === "none"
? ""
: undefined;
const engAPs = getEnglishAPs(VP.blocks);
const engAPs = getEngAPs(VP.blocks);
// require all English parts for making the English phrase
return (VP.englishBase && engSubj && engObj !== undefined)
? VP.englishBase.map(e => insertEWords(e, {
@ -438,27 +297,7 @@ function compileEnglishEP(EP: T.EPRendered): string[] | undefined {
return b;
}
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 checkEPForMiniPronounsError(s: T.EPSelectionState): undefined | string {
export function checkForMiniPronounsError(s: T.EPSelectionState | T.VPSelectionState): undefined | string {
function findDuplicateMiniP(mp: T.MiniPronoun[]): T.MiniPronoun | undefined {
const duplicates = mp.filter((item, index) => (
mp.findIndex(m => item.ps.p === m.ps.p) !== index
@ -467,10 +306,14 @@ export function checkEPForMiniPronounsError(s: T.EPSelectionState): undefined |
return duplicates[0];
}
const kids = (() => {
const EPS = completeEPSelection(s);
if (!EPS) return undefined;
const { kids } = renderEP(EPS);
return kids;
if ("predicate" in s) {
const EPS = completeEPSelection(s);
if (!EPS) return undefined;
return renderEP(EPS).kids;
};
const VPS = completeVPSelection(s);
if (!VPS) return undefined;
return renderVP(VPS).kids;
})();
if (!kids) return undefined;
const miniPronouns = kids.filter(x => x.type === "mini-pronoun") as T.MiniPronoun[];
@ -484,67 +327,6 @@ export function checkEPForMiniPronounsError(s: T.EPSelectionState): undefined |
return undefined;
}
export function checkForMiniPronounsError(s: 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 = (() => {
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 VP.blocks.reduce((found, block) => {
if (block.type === "subjectSelection") {
if (block.selection.selection.role === "king" && f.removedKing) return found;
if (block.selection.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.selection.role === "king" && f.removedKing) return found;
if (block.selection.selection.role === "servant" && f.shrunkServant) return found;
return [
...findPossesivesInNP(block.selection),
...found,
];
}
if (block.selection.type === "sandwich") {
if (block.selection.inside.selection.type === "pronoun") {
return found;
}
return [
...findPossesivesInNP(block.selection.inside),
...found,
];
}
return found;
}, [] as T.Rendered<T.NPSelection>[]);
}
function findPossesivesInNP(NP: T.Rendered<T.NPSelection> | T.ObjectNP | undefined): T.Rendered<T.NPSelection>[] {
if (NP === undefined) return [];
if (typeof NP !== "object") return [];
@ -574,24 +356,13 @@ function findPossesivesInAdjective(a: T.Rendered<T.AdjectiveSelection>): T.Rende
return findPossesivesInNP(a.sandwich.inside);
}
// export function findPossesiveToShrinkInVP(VP: T.VPRendered): T.Rendered<T.NPSelection>[] {
// const obj: T.Rendered<T.NPSelection> | undefined = ("object" in VP && typeof VP.object === "object")
// ? VP.object
// : undefined;
// return [
// ...findPossesivesInNP(VP.subject),
// ...findPossesivesInNP(obj),
// ];
// }
export function shrinkNP(np: T.Rendered<T.NPSelection>): Segment {
function getFirstSecThird(): 1 | 2 | 3 {
if ([0, 1, 6, 7].includes(np.selection.person)) return 1;
if ([2, 3, 8, 9].includes(np.selection.person)) return 2;
return 3;
export function flattenLengths(r: T.SingleOrLengthOpts<T.PsString[] | T.PsString>): T.PsString[] {
if ("long" in r) {
return Object.values(r).flat();
}
const [row, col] = getVerbBlockPosFromPerson(np.selection.person);
return makeSegment(pronouns.mini[row][col], ["isKid", getFirstSecThird()]);
if (Array.isArray(r)) {
return r;
}
return [r];
}

View File

@ -0,0 +1,108 @@
import * as T from "../../types";
import {
getPersonFromNP,
} from "./vp-tools";
import { pronouns } from "../grammar-units";
import { getVerbBlockPosFromPerson } from "../misc-helpers";
import { getFirstSecThird } from "../../lib/misc-helpers";
export function findPossesivesToShrink(
blocks: (T.EPSBlockComplete | T.VPSBlockComplete | T.SubjectSelectionComplete | T.PredicateSelectionComplete | T.APSelection)[],
): T.MiniPronoun[] {
return blocks.reduce((kids, item) => {
const block = "block" in item ? item.block : item;
if (block.type === "subjectSelection") {
return [
...kids,
...findShrunkenPossInNP(block.selection),
];
}
if (block.type === "objectSelection") {
if (typeof block.selection !== "object") return kids;
return [
...kids,
...findShrunkenPossInNP(block.selection),
];
}
if (block.type === "AP") {
if (block.selection.type === "adverb") return kids;
return [
...kids,
...findShrunkenPossInNP(block.selection.inside),
];
}
if (block.type === "predicateSelection") {
if (block.selection.type === "EQComp") {
if (block.selection.selection.type === "sandwich") {
return [
...kids,
...findShrunkenPossInNP(block.selection.selection.inside),
];
}
return kids;
}
return [
...kids,
...findShrunkenPossInNP(block.selection),
];
}
return kids;
}, [] as T.MiniPronoun[]);
}
function findShrunkenPossInNP(NP: T.NPSelection): T.MiniPronoun[] {
if (NP.selection.type === "pronoun") return [];
if (!NP.selection.possesor) return [];
// if (NP.selection.type === "noun") {
// if (NP.selection.adjectives) {
// const { adjectives, ...rest } = NP.selection;
// return [
// // TODO: ability to find possesives shrinkage in sandwiches in adjectives
// // ...findShrunkenPossInAdjectives(adjectives),
// ...findShrunkenPossInNP({ type: "NP", selection: {
// ...rest,
// adjectives: [],
// }}),
// ];
// }
// }
if (NP.selection.possesor.shrunken) {
const person = getPersonFromNP(NP.selection.possesor.np);
const miniP: T.MiniPronoun = {
type: "mini-pronoun",
person,
ps: getMiniPronounPs(person),
source: "possesive",
np: NP.selection.possesor.np,
};
return [miniP];
}
return findShrunkenPossInNP(NP.selection.possesor.np);
}
export function getMiniPronounPs(person: T.Person): T.PsString {
const [row, col] = getVerbBlockPosFromPerson(person);
return pronouns.mini[row][col][0];
}
export function orderKids(kids: T.Kid[]): T.Kid[] {
const sorted = [...kids].sort((a, b) => {
// ba first
if (a.type === "ba") return -1;
// kinds lined up 1st 2nd 3rd person
if (a.type === "mini-pronoun" && b.type === "mini-pronoun") {
const aPers = getFirstSecThird(a.person);
const bPers = getFirstSecThird(b.person);
if (aPers < bPers) {
return -1;
}
if (aPers > bPers) {
return 1;
}
// TODO: is this enough?
return 0;
}
return 0;
});
return sorted;
}

View File

@ -4,7 +4,7 @@ import {
getPersonFromNP,
} from "./vp-tools";
import { renderNPSelection } from "./render-np";
import { getFirstSecThird, getPersonFromVerbForm } from "../../lib/misc-helpers";
import { getPersonFromVerbForm } from "../../lib/misc-helpers";
import { getVerbBlockPosFromPerson } from "../misc-helpers";
import { getEnglishWord } from "../get-english-word";
import { psStringFromEntry } from "../p-text-helpers";
@ -13,7 +13,7 @@ import { renderAdjectiveSelection } from "./render-adj";
import { renderSandwich } from "./render-sandwich";
import { EPSBlocksAreComplete, getSubjectSelection } from "./blocks-utils";
import { removeAccentsWLength } from "../accent-helpers";
import { pronouns } from "../grammar-units";
import { findPossesivesToShrink, orderKids } from "./render-common";
export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
const { kids, blocks, englishEquativePerson } = getEPSBlocksAndKids(EP);
@ -29,7 +29,7 @@ export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
};
}
function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks: T.Block[], englishEquativePerson: T.Person } {
function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks: T.Block[][], englishEquativePerson: T.Person } {
const subject = getSubjectSelection(EP.blocks).selection;
const subjectPerson = getPersonFromNP(subject);
const commandingNP: T.NPSelection = subject.selection.type === "pronoun"
@ -39,7 +39,7 @@ function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks
: subject;
const commandingPerson = getPersonFromNP(commandingNP);
const equative: T.EquativeBlock = { type: "equative", equative: renderEquative(EP.equative, commandingPerson) };
const blocks: T.Block[] = [
const blocks: T.Block[][] = insertNegative([
...renderEPSBlocks(EP.blocks),
{
type: "predicateSelection",
@ -47,10 +47,9 @@ function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks
? renderNPSelection(EP.predicate.selection, false, false, "subject", "king")
: renderEqCompSelection(EP.predicate.selection, commandingPerson),
},
...EP.equative.negative ? [{ type: "nu" } as T.Block] : [],
EP.equative.negative ? removeAccontsFromEq(equative) : equative,
];
const miniPronouns = findPossesivesToShrink([...EP.blocks, EP.predicate], EP.omitSubject);
equative,
], EP.equative.negative);
const miniPronouns = findPossesivesToShrink(removeOrKeepSubject([...EP.blocks, EP.predicate], EP.omitSubject));
const kids: T.Kid[] = orderKids([
...equative.equative.hasBa ? [{ type: "ba" } as T.Kid] : [],
...miniPronouns,
@ -64,92 +63,27 @@ function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks
};
}
function orderKids(kids: T.Kid[]): T.Kid[] {
const sorted = [...kids].sort((a, b) => {
// ba first
if (a.type === "ba") return -1;
// kinds lined up 1st 2nd 3rd person
if (a.type === "mini-pronoun" && b.type === "mini-pronoun") {
const aPers = getFirstSecThird(a.person);
const bPers = getFirstSecThird(b.person);
if (aPers < bPers) {
return -1;
}
if (aPers > bPers) {
return 1;
}
// TODO: is this enough?
return 0;
}
return 0;
});
return sorted;
function insertNegative(blocks: T.Block[], negative: boolean): T.Block[][] {
if (!negative) return [blocks];
const blocksA = removeAccentsFromEq(blocks);
return [
[
...blocksA.slice(0, blocks.length - 1),
{ type: "negative", imperative: false },
...blocksA.slice(-1), // last (equative)
],
[
...blocksA.slice(0, blocks.length - 2),
{ type: "negative", imperative: false },
...blocksA.slice(-1), // last (equative)
...blocksA.slice(-2, -1), // second last (predicate)
],
];
}
function findPossesivesToShrink(blocks: (T.EPSBlockComplete | T.SubjectSelectionComplete | T.PredicateSelectionComplete | T.APSelection)[], omitSubject: boolean): T.MiniPronoun[] {
return blocks.reduce((kids, item) => {
const block = "block" in item ? item.block : item;
if (block.type === "subjectSelection") {
if (omitSubject) return kids;
return [
...kids,
...findShrunkenPossInNP(block.selection),
];
}
if (block.type === "AP") {
if (block.selection.type === "adverb") return kids;
return [
...kids,
...findShrunkenPossInNP(block.selection.inside),
];
}
if (block.type === "predicateSelection") {
if (block.selection.type === "EQComp") {
if (block.selection.selection.type === "sandwich") {
return [
...kids,
...findShrunkenPossInNP(block.selection.selection.inside),
];
}
return kids;
}
return [
...kids,
...findShrunkenPossInNP(block.selection),
];
}
return kids;
}, [] as T.MiniPronoun[]);
}
function findShrunkenPossInNP(NP: T.NPSelection): T.MiniPronoun[] {
if (NP.selection.type === "pronoun") return [];
if (!NP.selection.possesor) return [];
// if (NP.selection.type === "noun") {
// if (NP.selection.adjectives) {
// const { adjectives, ...rest } = NP.selection;
// return [
// // TODO: ability to find possesives shrinkage in sandwiches in adjectives
// // ...findShrunkenPossInAdjectives(adjectives),
// ...findShrunkenPossInNP({ type: "NP", selection: {
// ...rest,
// adjectives: [],
// }}),
// ];
// }
// }
if (NP.selection.possesor.shrunken) {
const person = getPersonFromNP(NP.selection.possesor.np);
const miniP: T.MiniPronoun = {
type: "mini-pronoun",
person,
ps: getMiniPronounPs(person),
source: "possesive",
np: NP.selection.possesor.np,
};
return [miniP];
}
return findShrunkenPossInNP(NP.selection.possesor.np);
function removeOrKeepSubject(blocks: (T.EPSBlockComplete | T.SubjectSelectionComplete | T.PredicateSelectionComplete | T.APSelection)[], omitSubject: boolean): (T.EPSBlockComplete | T.SubjectSelectionComplete | T.PredicateSelectionComplete | T.APSelection)[] {
if (!omitSubject) return blocks;
return blocks.filter(b => !("type" in b && b.type === "subjectSelection"));
}
export function getEquativeForm(tense: T.EquativeTense): { hasBa: boolean, form: T.SingleOrLengthOpts<T.VerbBlock> } {
@ -343,17 +277,18 @@ export function completeEPSelection(eps: T.EPSelectionState): T.EPSelectionCompl
};
}
export function getMiniPronounPs(person: T.Person): T.PsString {
const [row, col] = getVerbBlockPosFromPerson(person);
return pronouns.mini[row][col][0];
}
function removeAccontsFromEq(equ: T.EquativeBlock): T.EquativeBlock {
return {
...equ,
equative: {
...equ.equative,
ps: removeAccentsWLength(equ.equative.ps),
},
};
function removeAccentsFromEq(blocks: T.Block[]): T.Block[] {
return blocks.map((block) => {
if (block.type === "equative") {
const e: T.EquativeBlock = {
...block,
equative: {
...block.equative,
ps: removeAccentsWLength(block.equative.ps),
},
};
return e;
}
return block;
});
}

View File

@ -17,8 +17,8 @@ import { renderAdjectiveSelection } from "./render-adj";
import { isPattern5Entry, isAnimNounEntry } from "../type-predicates";
export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflectEnglish: boolean, role: "subject", soRole: "servant" | "king" | "none"): T.Rendered<T.NPSelection>;
export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "object", soRole: "servant" | "king" | "none"): T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale | "none";
export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "subject" | "object", soRole: "servant" | "king" | "none"): T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale | "none" {
export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflectEnglish: boolean, role: "object", soRole: "servant" | "king" | "none"): T.Rendered<T.NPSelection>;
export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflectEnglish: boolean, role: "subject" | "object", soRole: "servant" | "king" | "none"): T.Rendered<T.NPSelection> {
if (typeof NP !== "object") {
if (role !== "object") {
throw new Error("ObjectNP only allowed for objects");

View File

@ -7,8 +7,10 @@ import {
hasBaParticle,
getLong,
isImperativeBlock,
splitOffLeapfrogWordFull,
getShort,
} from "../p-text-helpers";
import { removeAccents } from "../accent-helpers";
import { removeAccents, removeAccentsWLength } from "../accent-helpers";
import {
getPersonFromNP,
removeBa,
@ -16,6 +18,8 @@ import {
getTenseVerbForm,
} from "./vp-tools";
import {
isImperativeTense,
isModalTense,
isPattern4Entry,
isPerfectTense,
} from "../type-predicates";
@ -24,6 +28,7 @@ import { personGender } from "../../lib/misc-helpers";
import { renderNPSelection } from "./render-np";
import { getObjectSelection, getSubjectSelection } from "./blocks-utils";
import { renderAPSelection } from "./render-ap";
import { findPossesivesToShrink, orderKids, getMiniPronounPs } from "./render-common";
// TODO: ISSUE GETTING SPLIT HEAD NOT MATCHING WITH FUTURE VERBS
@ -47,6 +52,12 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
const inflectSubject = isPast && isTransitive && !isMascSingAnimatePattern4(subject);
const inflectObject = !isPast && isFirstOrSecondPersPronoun(object);
// Render Elements
const firstBlocks = renderVPBlocks(VP.blocks, {
inflectSubject,
inflectObject,
king,
});
const { verbBlocks, hasBa } = renderVerbSelection(VP.verb, kingPerson, objectPerson);
const b: T.VPRendered = {
type: "VPRendered",
king,
@ -54,12 +65,11 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
isPast,
isTransitive,
isCompound: VP.verb.isCompound,
blocks: renderVPBlocks(VP.blocks, {
inflectSubject,
inflectObject,
king,
}),
verb: renderVerbSelection(VP.verb, kingPerson, objectPerson),
blocks: insertNegative([
...firstBlocks,
...verbBlocks,
], VP.verb.negative, isImperativeTense(VP.verb.tense)),
kids: getVPKids(hasBa, VP.blocks, VP.form, king),
englishBase: renderEnglishVPBase({
subjectPerson,
object: VP.verb.isCompound === "dynamic" ? "none" : object,
@ -71,28 +81,271 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
return b;
}
// function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[], V: T.VerbRendered): Segment[][] {
// const hasLeapfrog = isPerfectTense(V.tense) || isModalTense(V.tense);
// const rest = (() => {
// if (hasLeapfrog) {
// const [restF, restLast] = splitOffLeapfrogWord(restRaw);
// return {
// front: makeSegment(restF.map(removeBa), ["isVerbRest"]),
// last: makeSegment(restLast.map(removeBa), ["isVerbRest"]),
// };
// }
// return makeSegment(restRaw.map(removeBa), ["isVerbRest"]);
// })();
// const headSegment: Segment | undefined = !head
// ? head
// : makeSegment(
// head,
// (head.p === "و" || head.p === "وا")
// ? ["isVerbHead", "isOoOrWaaHead"]
// : ["isVerbHead"]
// );
// if (!V.negative) {
// if ("front" in rest) {
// return [
// headSegment ? [headSegment, rest.front, rest.last] : [rest.front, rest.last],
// ]
// }
// return [
// headSegment ? [headSegment, rest] : [rest],
// ];
// }
// const nu: T.PsString = isImperativeTense(V.tense)
// ? { p: "مه", f: "mú" }
// : { p: "نه", f: "nú" };
// if (!headSegment) {
// if ("front" in rest) {
// return [
// // pefect nu dey me leeduley and nu me dey leeduley
// // actually don't think this is correct - keeping it out for now
// // [
// // mergeSegments(
// // makeSegment(nu, ["isNu"]),
// // rest.last.adjust({ ps: removeAccents }),
// // ),
// // rest.front.adjust({ ps: removeAccents }),
// // ],
// [
// makeSegment(nu, ["isNu"]),
// rest.last.adjust({ ps: removeAccents }),
// rest.front.adjust({ ps: removeAccents }),
// ],
// [
// rest.front.adjust({ ps: removeAccents }),
// makeSegment(nu, ["isNu"]),
// rest.last.adjust({ ps: removeAccents }),
// ],
// ];
// }
// return [[
// makeSegment(nu, ["isNu"]),
// rest.adjust({ ps: removeAccents }),
// ]];
// }
// if ("front" in rest) {
// return [
// [
// headSegment.adjust({ ps: removeAccents }),
// rest.last.adjust({
// ps: r => concatPsString(nu, " ", removeAccents(r)),
// desc: ["isNu"],
// }),
// rest.front.adjust({
// ps: r => removeAccents(r),
// }),
// ],
// [
// headSegment.adjust({ ps: removeAccents }),
// rest.front.adjust({
// ps: r => concatPsString(nu, " ", removeAccents(r)),
// desc: ["isNu"],
// }),
// rest.last.adjust({
// ps: r => removeAccents(r),
// }),
// ],
// ...(!headSegment.isOoOrWaaHead && !V.isCompound) ? [[
// mergeSegments(headSegment, rest.front, "no space").adjust({
// ps: r => concatPsString(nu, " ", removeAccents(r)),
// desc: ["isNu"],
// }),
// rest.last.adjust({
// ps: r => removeAccents(r),
// }),
// ]] : [],
// ];
// }
// return [
// ...(V.voice !== "passive") ? [[
// ...headSegment ? [headSegment.adjust({ ps: removeAccents })] : [],
// rest.adjust({
// ps: r => concatPsString(nu, " ", removeAccents(r)),
// desc: ["isNu"],
// }),
// ]] : [],
// // verbs that have a perfective prefix that is not و or وا can put the
// // nu *before* the prefix as well // TODO: also وي prefixes?
// ...((!headSegment.isOoOrWaaHead && !V.isCompound) || (V.voice === "passive")) ? [[
// makeSegment(nu, ["isNu"]),
// headSegment.adjust({ ps: removeAccents }),
// rest.adjust({ ps: removeAccents }),
// ]] : [],
// ];
// }
function getVPKids(hasBa: boolean, blocks: T.VPSBlockComplete[], form: T.FormVersion, king: "subject" | "object"): T.Kid[] {
const subject = getSubjectSelection(blocks).selection;
const objectS = getObjectSelection(blocks).selection;
const object = typeof objectS === "object" ? objectS : undefined;
const servantNP = king === "subject" ? object : subject;
const shrunkenServant = (form.shrinkServant && servantNP)
? shrinkServant(servantNP)
: undefined;
const shrunkenPossesives = findPossesivesToShrink(removeAbbreviated(blocks, form, king));
return orderKids([
...hasBa ? [{ type: "ba" } as T.Kid] : [],
...shrunkenServant ? [shrunkenServant] : [],
...shrunkenPossesives ? shrunkenPossesives : [],
]);
}
function removeAbbreviated(blocks: T.VPSBlockComplete[], form: T.FormVersion, king: "subject" | "object"): T.VPSBlockComplete[] {
return blocks.filter(({ block }) => {
if (block.type === "subjectSelection") {
if (form.shrinkServant && king === "object") return false;
if (form.removeKing && king === "subject") return false;
}
if (block.type === "objectSelection") {
if (form.shrinkServant && king === "subject") return false;
if (form.removeKing && king === "object") return false;
}
return true;
})
}
function insertNegative(blocks: T.Block[], negative: boolean, imperative: boolean): T.Block[][] {
// TODO: proper arrange verb with negative and variations
// ALSO removing the accent from nu
if (!negative) return [blocks];
const blocksA = removeVerbAccent(blocks);
const basic: T.Block[] = [
...blocksA.slice(0, blocks.length - 1),
{
type: "negative",
imperative,
},
...blocksA.slice(-1),
];
if (hasNonStandardPerfectiveSplit(blocks)) {
return [
basic,
[
...blocksA.slice(0, blocks.length - 2),
{ type: "negative", imperative },
...blocksA.slice(-2, -1), // second last (perfective split)
...blocksA.slice(-1), // last (verb)
],
];
}
if (hasLeapFroggable(blocks)) {
return [
[
...blocksA.slice(0, blocks.length - 2),
{ type: "negative", imperative },
...blocksA.slice(-1), // last
...blocksA.slice(-2, -1), // second last
],
basic,
];
}
return [basic];
}
function hasLeapFroggable(blocks: T.Block[]): boolean {
return blocks.some(b => b.type === "perfectEquativeBlock" || b.type === "modalVerbBlock");
}
function hasNonStandardPerfectiveSplit(blocks: T.Block[]): boolean {
const perfS = blocks.find(b => b.type === "perfectiveHead");
if (!perfS || perfS.type !== "perfectiveHead") {
return false;
}
return !["و", "وا"].includes(perfS.ps.p);
}
function removeVerbAccent(blocks: T.Block[]): T.Block[] {
return blocks.map((block) => {
if (block.type === "perfectiveHead") {
return {
...block,
ps: removeAccents(block.ps),
};
}
if (block.type === "verb") {
return {
...block,
block: {
...block.block,
ps: removeAccentsWLength(block.block.ps),
},
};
}
return block;
});
}
function shrinkServant(np: T.NPSelection): T.MiniPronoun {
const person = getPersonFromNP(np);
return {
type: "mini-pronoun",
person,
ps: getMiniPronounPs(person),
source: "servant",
np,
};
}
function renderVPBlocks(blocks: T.VPSBlockComplete[], config: {
inflectSubject: boolean,
inflectObject: boolean,
king: "subject" | "object",
}): T.RenderedVPSBlock[] {
return blocks.map(({ block }): T.RenderedVPSBlock => {
}): T.Block[] {
return blocks.reduce((blocks, { block }): T.Block[] => {
if (block.type === "subjectSelection") {
return {
type: "subjectSelection",
selection: renderNPSelection(block.selection, config.inflectSubject, false, "subject", config.king === "subject" ? "king" : "servant"),
}
return [
...blocks,
{
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;
if (typeof object !== "object") {
return [
...blocks,
{
type: "objectSelection",
selection: object,
},
];
}
const selection = renderNPSelection(object, config.inflectObject, true, "object", config.king === "object" ? "king" : "servant");
return {
type: "objectSelection",
selection,
};
return [
...blocks,
{
type: "objectSelection",
selection,
},
];
}
return renderAPSelection(block);
});
return [
...blocks,
renderAPSelection(block),
];
}, [] as T.Block[]);
}
function whatsAdjustable(VP: T.VPSelectionComplete): "both" | "king" | "servant" {
@ -109,7 +362,15 @@ function whatsAdjustable(VP: T.VPSelectionComplete): "both" | "king" | "servant"
: "king";
}
function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): T.VerbRendered {
type VerbBlocks = | [T.PerfectiveHeadBlock, T.VerbRenderedBlock] // verb w perfective split
| [T.VerbRenderedBlock] // verb w/out perfective split
| [T.PerfectParticipleBlock, T.PerfectEquativeBlock] // perfect verb
| [T.ModalVerbBlock, T.ModalVerbKedulPart] // modal verb
function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): {
verbBlocks: VerbBlocks
hasBa: boolean,
} {
const v = vs.dynAuxVerb || vs.verb;
const conjugations = conjugateVerb(v.entry, v.complement);
// TODO: error handle this?
@ -118,14 +379,73 @@ function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, obje
// will default to transitive
? conjugations.transitive
: "stative" in conjugations
// TODO: option to manually select stative/dynamic
? conjugations.stative
: conjugations;
: conjugations;
const { ps: { head, rest }, hasBa } = getPsVerbConjugation(conj, vs, person, objectPerson);
const vrb: T.VerbRenderedBlock = {
type: "verb",
block: {
...vs,
ps: rest,
person,
hasBa,
},
};
const verbBlocks = [
...(head ? (
vs.isCompound === "stative" ? [{
type: "verbComplement",
complement: head,
} as T.VerbComplementBlock] : [{
type: "perfectiveHead",
ps: head,
} as T.PerfectiveHeadBlock]
) : [] as [T.VerbComplementBlock] | [T.PerfectiveHeadBlock] | []),
...splitUpIfModal(vrb),
] as VerbBlocks;
const perfectStuff = isPerfectTense(vrb.block.tense) ? getPerfectStuff(rest, vrb) : undefined;
return {
...vs,
person,
...getPsVerbConjugation(conj, vs, person, objectPerson),
verbBlocks: perfectStuff ? perfectStuff : verbBlocks,
hasBa,
};
}
function splitUpIfModal(v: T.VerbRenderedBlock): [T.VerbRenderedBlock] | [T.ModalVerbBlock, T.ModalVerbKedulPart] {
if (!isModalTense(v.block.tense)) {
return [v];
}
const [vrb, k] = splitOffLeapfrogWordFull(v.block.ps);
return [
{
type: "modalVerbBlock",
ps: vrb,
verb: v,
},
{
type: "modalVerbKedulPart",
// sadly just for type safety - the conjugator always just gives us the short form
ps: getShort(k),
verb: v,
},
];
}
function getPerfectStuff(v: T.SingleOrLengthOpts<T.PsString[]>, vrb: T.VerbRenderedBlock): [T.PerfectParticipleBlock, T.PerfectEquativeBlock] {
const [p, eq] = splitOffLeapfrogWordFull(v);
return [
{
type: "perfectParticipleBlock",
ps: p,
person: vrb.block.person,
verb: vrb,
},
{
type: "perfectEquativeBlock",
// TODO: right now the conjugator just always spits out the short form of the equative - would be nice to have both
ps: getShort(eq),
person: vrb.block.person,
},
];
}
function getPsVerbConjugation(conj: T.VerbConjugation, vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): {
@ -156,7 +476,20 @@ function getPsVerbConjugation(conj: T.VerbConjugation, vs: T.VerbSelectionComple
ps,
};
}
return { hasBa, ps: { head: undefined, rest: verbForm }};
return { hasBa, ps: { head: undefined, rest: removeBaFromForm(verbForm) }};
}
function removeBaFromForm(f: T.SingleOrLengthOpts<T.PsString[]>): T.SingleOrLengthOpts<T.PsString[]> {
if ("long" in f) {
return {
long: removeBaFromForm(f.long) as T.PsString[],
short: removeBaFromForm(f.short) as T.PsString[],
...f.mini ? {
mini: removeBaFromForm(f.mini) as T.PsString[],
} : {},
};
}
return f.map(removeBa);
}
function getVerbFromBlock(block: T.SingleOrLengthOpts<T.VerbBlock | T.ImperativeBlock>, person: T.Person): T.SingleOrLengthOpts<T.PsString[]> {

View File

@ -1,120 +0,0 @@
import * as T from "../../types";
import {
makePsString,
} from "../accent-and-ps-utils";
import {
concatPsString,
} from "../p-text-helpers";
type SegmentDescriptions = {
isVerbHead?: boolean,
isOoOrWaaHead?: boolean,
isVerbRest?: boolean,
isMiniPronoun?: number,
isKid?: boolean,
// TODO: Simplify to just isKidAfterHead?
isKidBetweenHeadAndRest?: boolean,
isNu?: boolean,
isBa?: boolean,
}
type SDT = keyof SegmentDescriptions;
export type Segment = { ps: T.PsString[] } & SegmentDescriptions & {
adjust: (o: { ps?: T.PsString | T.PsString[] | ((ps: T.PsString) => T.PsString), desc?: SDT[] }) => Segment,
};
export function makeSegment(
input: T.PsString | T.PsString[],
options?: (keyof SegmentDescriptions | 1 | 2 | 3)[],
): Segment {
const ps: T.PsString[] = Array.isArray(input)
? input
: [input];
return {
ps: Array.isArray(ps) ? ps : [ps],
...options && options.reduce((all, curr) => ({
...all,
...typeof curr === "number" ? {
isMiniPronoun: curr,
} : {
[curr]: true,
},
}), {}),
adjust: function(o): Segment {
return {
...this,
...o.ps ? {
// TODO: is this ok with the adjectives?
ps: Array.isArray(o.ps)
? o.ps
: "p" in o.ps
? [o.ps]
: this.ps.map(o.ps)
} : {},
...o.desc && o.desc.reduce((all, curr) => ({
...all,
[curr]: true,
}), {}),
};
},
};
}
export function combineSegments(loe: (Segment | " " | "" | T.PsString)[], spaces?: "spaces"): T.PsString[] {
const first = loe[0];
const rest = loe.slice(1);
if (!rest.length) {
if (typeof first === "string" || !("ps" in first)) {
throw new Error("can't end with a spacer");
}
return first.ps;
}
return combineSegments(rest, spaces).flatMap(r => (
(typeof first === "object" && "ps" in first)
? first.ps.map(f => (
spaces ? concatPsString(f, " ", r) : concatPsString(f, r)
))
: [concatPsString(first, r)]
)
);
}
export function flattenLengths(r: T.SingleOrLengthOpts<T.PsString[] | T.PsString>): T.PsString[] {
if ("long" in r) {
return Object.values(r).flat();
}
if (Array.isArray(r)) {
return r;
}
return [r];
}
export function putKidsInKidsSection(segments: Segment[], kids: Segment[]): Segment[] {
const first = segments[0];
const rest = segments.slice(1);
return [
first,
// TODO: simplify to just isKidAfterHead ??
...(first.isVerbHead && rest[0] && rest[0].isVerbRest)
? kids.map(k => k.adjust({ desc: ["isKidBetweenHeadAndRest"] }))
: kids,
...rest,
];
}
export function splitOffLeapfrogWord(psVs: T.PsString[]): [T.PsString[], T.PsString[]] {
return psVs.reduce((tp, ps) => {
const pWords = ps.p.split(" ");
const fWords = ps.f.split(" ");
const beginning = makePsString(
pWords.slice(0, -1).join(" "),
fWords.slice(0, -1).join(" "),
);
const end = makePsString(
pWords.slice(-1).join(" "),
fWords.slice(-1).join(" "),
);
return [[...tp[0], beginning], [...tp[1], end]];
}, [[], []] as [T.PsString[], T.PsString[]]);
}

View File

@ -155,7 +155,7 @@ export function getTenseFromVerbSelection(vs: T.VerbSelection): T.VerbTense | T.
return "perfectiveFutureModal";
}
if (tn === "perfectivePast") {
return "perfectiveFutureModal";
return "perfectivePastModal";
}
if (tn === "imperfectivePast") {
return "imperfectivePastModal";

View File

@ -1006,14 +1006,6 @@ function getPassiveStemPerfectiveSplit(stem: T.OptionalPersonInflections<T.Lengt
function getPassiveRootPerfectiveSplit(root: T.OptionalPersonInflections<T.LengthOptions<T.PsString>>, splitInfo: T.SplitInfo): T.SplitInfo {
const si = "long" in splitInfo ? splitInfo.long : splitInfo;
// if ("long" in splitInfo) {
// return {
// // @ts-ignore
// short: getPassiveRootPerfectiveSplit(root, splitInfo.long, "short"),
// // @ts-ignore
// long: getPassiveRootPerfectiveSplit(root, splitInfo.long, "long"),
// };
// }
if ("mascSing" in si) {
if (!("mascSing" in root)) throw new Error("persInflections doesn't match perfective split");
return {

View File

@ -15,7 +15,6 @@ import {
import {
getVerbInfo,
} from "./lib/verb-info";
import ConjugationViewer from "./components/ConjugationViewer";
import InflectionsTable from "./components/InflectionsTable";
import Pashto from "./components/Pashto";
import Phonetics from "./components/Phonetics";
@ -113,7 +112,7 @@ import {
} from "./lib/misc-helpers";
import {
flattenLengths,
} from "./lib/phrase-building/segment";
} from "./lib/phrase-building/compile";
import {
simplifyPhonetics,
} from "./lib/simplify-phonetics";
@ -235,7 +234,6 @@ export {
// COMPONENTS
EPExplorer,
VPExplorer,
ConjugationViewer, // TODO: Deprecated - remove
Examples,
VerbFormDisplay,
VerbTable,

View File

@ -505,7 +505,6 @@ 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",
@ -514,8 +513,8 @@ export type VPRendered = {
isPast: boolean,
isTransitive: boolean,
isCompound: "stative" | "dynamic" | false,
blocks: RenderedVPSBlock[],
verb: VerbRendered,
blocks: Block[][],
kids: Kid[],
englishBase?: string[],
form: FormVersion,
whatsAdjustable: "both" | "king" | "servant",
@ -838,7 +837,7 @@ export type EquativeRendered = EquativeSelection & {
export type EPRendered = {
type: "EPRendered",
blocks: Block[],
blocks: Block[][],
kids: Kid[],
englishBase?: string[],
omitSubject: boolean,
@ -866,12 +865,54 @@ export type EntryLookupPortal<X extends VerbEntry | DictionaryEntry> = {
}
export type EquativeBlock = { type: "equative", equative: EquativeRendered };
export type VerbComplementBlock = {
type: "verbComplement",
complement: PsString,
};
export type PerfectParticipleBlock = {
type: "perfectParticipleBlock",
ps: SingleOrLengthOpts<PsString[]>,
verb: VerbRenderedBlock,
person: Person,
};
export type PerfectEquativeBlock = {
type: "perfectEquativeBlock",
ps: PsString[],
person: Person,
};
export type ModalVerbBlock = {
type: "modalVerbBlock",
ps: SingleOrLengthOpts<PsString[]>,
verb: VerbRenderedBlock,
};
export type ModalVerbKedulPart = {
type: "modalVerbKedulPart",
ps: PsString[],
verb: VerbRenderedBlock,
};
export type PerfectiveHeadBlock = { type: "perfectiveHead", ps: PsString };
export type VerbRenderedBlock = {
type: "verb",
block: Omit<VerbSelectionComplete, "object"> & {
hasBa: boolean,
ps: SingleOrLengthOpts<PsString[]>,
person: Person,
},
};
export type Block =
| Rendered<SubjectSelectionComplete>
| Rendered<ObjectSelectionComplete>
| Rendered<APSelection>
| Rendered<PredicateSelectionComplete>
| { type: "nu" }
| PerfectParticipleBlock
| PerfectEquativeBlock
| ModalVerbBlock
| ModalVerbKedulPart
| { type: "negative", imperative: boolean }
| PerfectiveHeadBlock
| VerbRenderedBlock
| VerbComplementBlock
| EquativeBlock;
export type Kid =