added possesives! - still not quite there with participles

This commit is contained in:
lingdocs 2022-05-02 15:14:24 -05:00
parent c469c0f2f3
commit e2a90e6315
24 changed files with 482 additions and 180 deletions

View File

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

@ -27,6 +27,7 @@ import { isAdjectiveEntry, isLocativeAdverbEntry, isNounEntry } from "./lib/type
import defualtTextOptions from "./lib/default-text-options";
import PhraseBuilder from "./components/vp-explorer/VPExplorer";
import useStickyState from "./lib/useStickyState";
import { EPExplorer } from "./library";
type VerbType = "simple" | "stative compound" | "dynamic compound";
const verbTypes: VerbType[] = [
"simple",
@ -276,6 +277,11 @@ function App() {
opts={textOptions}
/>
</div>}
<h4>🆕 Equative Phrase Builder</h4>
<EPExplorer
opts={textOptions}
entryFeeder={entryFeeder}
/>
</div>
</main>
<Modal show={showingTextOptions} onHide={() => setShowingTextOptions(false)}>

View File

@ -15,12 +15,14 @@ import { getForms } from "../lib/conjugation-forms";
import { conjugateVerb } from "../lib/verb-conjugation";
import PersonSelection from "./PersonSelection";
import {
personIsAllowed,
randomPerson,
incrementPerson,
parseEc,
} from "../lib/misc-helpers";
import * as T from "../types";
import {
randomPerson,
isInvalidSubjObjCombo,
} from "../lib/np-tools";
const VerbChoiceWarning = () => (
<>
@ -100,7 +102,7 @@ function reducer(state: State, action: Action): State {
let newPerson = person;
let otherPerson = state[oppositeRole(setting)];
let otherSetting = oppositeRole(setting);
while (!personIsAllowed(newPerson, otherPerson)) {
while (isInvalidSubjObjCombo(newPerson, otherPerson)) {
otherPerson = incrementPerson(otherPerson);
}
return { ...state, [setting]: newPerson, [otherSetting]: otherPerson };
@ -127,9 +129,9 @@ function reducer(state: State, action: Action): State {
case "randomPerson":
return {
...state,
[action.payload]: randomPerson(
state[action.payload === "subject" ? "object" : "subject"]
),
[action.payload]: randomPerson({
prev: state[action.payload === "subject" ? "object" : "subject"]
}),
};
case "setShowingFormInfo":
return {

View File

@ -31,7 +31,8 @@ function EPExplorer(props: {
tense: "present",
negative: false,
},
}, "EPSelectionState2");
shrunkenPossesive: undefined,
}, "EPSelectionState3");
function handlePredicateTypeChange(type: "NP" | "Complement") {
setEps(o => ({
...o,
@ -56,6 +57,12 @@ function EPExplorer(props: {
},
}));
}
function handleShrinkPossesive(shrunkenPossesive: number | undefined) {
setEps(o => ({
...o,
shrunkenPossesive,
}));
}
const king = eps.subject?.type === "pronoun"
? "subject"
: eps.predicate.type === "Complement"
@ -76,6 +83,8 @@ function EPExplorer(props: {
{mode === "phrases" && <>
<div className="my-2">
<NPPicker
shrunkenPossesiveInPhrase={eps.shrunkenPossesive}
handleShrinkPossesive={handleShrinkPossesive}
heading={<div className="h5 text-center">Subject {king === "subject" ? roleIcon.king : ""}</div>}
entryFeeder={props.entryFeeder}
np={eps.subject}
@ -99,6 +108,8 @@ function EPExplorer(props: {
/>
</div>
{eps.predicate.type === "NP" ? <NPPicker
shrunkenPossesiveInPhrase={eps.shrunkenPossesive}
handleShrinkPossesive={handleShrinkPossesive}
entryFeeder={props.entryFeeder}
np={eps.predicate.type === "NP" ? eps.predicate.NP : undefined}
counterPart={undefined}

View File

@ -36,14 +36,12 @@ function AdjectiveManager(props: {
// flippedList.reverse();
// console.log(props.adjectives);
return <div className="mb-1">
{!!props.adjectives.length && <div className="d-flex flex-row justify-content-between">
<h6>Adjectives</h6>
{!adding ? <h6 onClick={() => setAdding(true)}>+ Adj.</h6> : <div></div>}
</div>}
{adding && <div>
<div className="d-flex flex-row justify-content-between">
<div className="d-flex flex-row justify-content-between mb-1">
<div>Add Adjective</div>
<div onClick={() => setAdding(false)}>Cancel</div>
<div className="clickable" onClick={() => setAdding(false)}>
<i className="fas fa-trash" />
</div>
</div>
<AdjectivePicker
noTitle
@ -53,21 +51,27 @@ function AdjectiveManager(props: {
onChange={handleAddNew}
/>
</div>}
{props.adjectives.map((adj, i) => (
<div className="d-flex flex-row align-items-baseline">
<AdjectivePicker
noTitle
key={`adj${i}`}
adjective={adj}
entryFeeder={props.entryFeeder}
opts={props.opts}
onChange={handleChange(i)}
/>
<div onClick={deleteAdj(i)} className="ml-4">
<div className="fas fa-trash" />
{props.adjectives.map((adj, i) => <div key={i}>
<div className="d-flex flex-row justify-content-between">
<div>Adjective</div>
<div className="d-flex flex-row align-items-baseline">
{!!props.adjectives.length && !adding && <div>
<h6 onClick={() => setAdding(true)}>+ Adj.</h6>
</div>}
<div onClick={deleteAdj(i)} className="ml-4">
<div className="fas fa-trash" />
</div>
</div>
</div>
))}
<AdjectivePicker
noTitle
key={`adj${i}`}
adjective={adj}
entryFeeder={props.entryFeeder}
opts={props.opts}
onChange={handleChange(i)}
/>
</div>)}
{!adding && !props.adjectives.length && <h6 className="clickable" style={{ float: "right" }}>
<div onClick={() => setAdding(true)}>+ Adj.</div>
</h6>}

View File

@ -5,6 +5,7 @@ function makeParticipleSelection(verb: T.VerbEntry): T.ParticipleSelection {
return {
type: "participle",
verb,
possesor: undefined,
};
}

View File

@ -18,8 +18,10 @@ function NPPicker(props: {
onChange: (nps: T.NPSelection | undefined) => void,
np: T.NPSelection | undefined,
counterPart: T.NPSelection | T.VerbObject | undefined,
role: "subject" | "object" | "ergative",
role: "subject" | "object" | "ergative" | "possesor",
opts: T.TextOptions,
handleShrinkPossesive: (uid: number | undefined) => void,
shrunkenPossesiveInPhrase: number | undefined,
cantClear?: boolean,
is2ndPersonPicker?: boolean,
entryFeeder: T.EntryFeeder,
@ -27,6 +29,7 @@ function NPPicker(props: {
if (props.is2ndPersonPicker && ((props.np?.type !== "pronoun") || !isSecondPerson(props.np.person))) {
throw new Error("can't use 2ndPerson NPPicker without a pronoun");
}
const [addingPoss, setAddingPoss] = useState<boolean>(false);
const [npType, setNpType] = useState<T.NPType | undefined>(props.np ? props.np.type : undefined);
useEffect(() => {
setNpType(props.np ? props.np.type : undefined);
@ -51,10 +54,32 @@ function NPPicker(props: {
setNpType(ntp);
}
}
function handlePossesiveChange(p: T.NPSelection | undefined) {
if (!props.np || props.np.type === "pronoun") return;
if (!p) {
props.onChange({
...props.np,
possesor: undefined,
});
return;
}
const isNewPosesser = checkForNewPossesor(p, props.np.possesor);
const possesor = {
np: p,
uid: (!isNewPosesser && props.np.possesor) ? props.np.possesor.uid : makeUID(),
};
props.onChange({
...props.np,
possesor,
});
}
const isDynamicComplement = props.np && props.np.type === "noun" && props.np.dynamicComplement;
const clearButton = (!props.cantClear && !props.is2ndPersonPicker && !isDynamicComplement)
? <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>
: <div></div>;
const possesiveUid = (props.np && props.np.type !== "pronoun" && props.np.possesor)
? props.np.possesor.uid
: undefined;
return <>
<div className="d-flex flex-row justify-content-between">
<div></div>
@ -68,21 +93,54 @@ function NPPicker(props: {
</div>
</div>
<div style={{ minWidth: "9rem" }}>
{!npType && <div className="text-center">
<div className="h6 mr-3">
Choose NP
</div>
{npTypes.map((npt) => <div key={npt} className="mb-2">
<button
key={npt}
type="button"
className="mr-2 btn btn-sm btn-outline-secondary"
onClick={() => handleNPTypeChange(npt)}
>
{npt}
</button>
{!npType && <div className="text-center">
<div className="h6 mr-3">
Choose NP
</div>
{npTypes.map((npt) => <div key={npt} className="mb-2">
<button
key={npt}
type="button"
className="mr-2 btn btn-sm btn-outline-secondary"
onClick={() => handleNPTypeChange(npt)}
>
{npt}
</button>
</div>)}
</div>}
{(props.np && props.np.type !== "pronoun" && (props.np.possesor || addingPoss)) && <div className="mb-3" style={{ paddingLeft: "0.5rem", borderLeft: "1px solid grey" }}>
<div className="d-flex flex-row text-muted mb-2">
<div>Possesive:</div>
{props.np.possesor && <div className="clickable mx-2" onClick={() => {
props.handleShrinkPossesive(possesiveUid === props.shrunkenPossesiveInPhrase
? undefined
: possesiveUid
);
}}>
{possesiveUid === props.shrunkenPossesiveInPhrase ? "👶 Shrunken" : "Shrink"}
</div>}
<div className="clickable ml-2" onClick={() => {
setAddingPoss(false);
handlePossesiveChange(undefined);
}}>
<i className="fas fa-trash" />
</div>
</div>
<NPPicker
onChange={handlePossesiveChange}
counterPart={undefined}
cantClear
np={props.np.possesor ? props.np.possesor.np : undefined}
handleShrinkPossesive={props.handleShrinkPossesive}
shrunkenPossesiveInPhrase={props.shrunkenPossesiveInPhrase}
role="possesor"
opts={props.opts}
entryFeeder={props.entryFeeder}
/>
</div>}
{(npType === "noun" || npType === "participle") && props.np && !addingPoss && <div>
<span className="clickable text-muted" onClick={() => setAddingPoss(true)}>+ Possesive</span>
</div>}
{(npType === "pronoun" && props.np?.type === "pronoun")
? <PronounPicker
role={props.role}
@ -111,6 +169,26 @@ function NPPicker(props: {
</>;
}
// {(npType && !isDynamicComplement) && }
function checkForNewPossesor(n: T.NPSelection | undefined, old: T.PossesorSelection | undefined): boolean {
if (!old || !n) {
return true;
}
if (n.type !== old.np.type) {
return true;
}
if (n.type === "pronoun") return false;
if (n.type === "noun" && old.np.type === "noun") {
return n.entry.ts !== old.np.entry.ts;
}
if (n.type === "participle" && old.np.type === "participle") {
return n.verb.entry.ts !== old.np.verb.entry.ts;
}
return false;
}
// TODO: BETTER UID
function makeUID() {
return Math.floor(Math.random() * 50000);
}
export default NPPicker;

View File

@ -12,7 +12,7 @@ const gColors = {
};
// TODO: better logic on this
const labels = (role: "subject" | "object" | "ergative") => ({
const labels = (role: "subject" | "object" | "ergative" | "possesor") => ({
// persons: [
// ["1st", "1st pl."],
// ["2nd", "2nd pl."],
@ -20,11 +20,15 @@ const labels = (role: "subject" | "object" | "ergative") => ({
// ],
e: role === "object" ? [
["me", "us"],
["you", "you pl."],
["you", "y'all"],
[{ masc: "him/it", fem: "her/it"}, "them"],
] : role === "possesor" ? [
["my", "our"],
["your", "y'all's"],
[{ masc: "his/its", fem: "her/its"}, "their"],
] : [
["I", "We"],
["You", "You pl."],
["You", "Y'all"],
[{ masc: "He/It", fem: "She/It"}, "They"],
],
p: role === "subject" ? {
@ -49,6 +53,17 @@ const labels = (role: "subject" | "object" | "ergative") => ({
["ته", "تاسو"],
[{ masc: "دهٔ", fem: "دې" }, "دوي"],
],
} : role === "possesor" ? {
far: [
["زما", "زمونږ"],
["ستا", "ستاسو"],
[{ masc: "د هغهٔ", fem: "د هغې" }, "د هغوي"],
],
near: [
["زما", "زمونږ"],
["ستا", "ستاسو"],
[{ masc: "د دهٔ", fem: "د دې" }, "د دوي"],
],
} : {
far: [
["ما", "مونږ"],
@ -79,11 +94,10 @@ function pickerStateToPerson(s: PickerState): T.Person {
+ (6 * s.col);
}
function NPPronounPicker({ onChange, pronoun, role, clearButton, opts, is2ndPersonPicker }: {
function NPPronounPicker({ onChange, pronoun, role, opts, is2ndPersonPicker }: {
pronoun: T.PronounSelection,
onChange: (p: T.PronounSelection) => void,
role: "object" | "subject" | "ergative",
clearButton?: JSX.Element,
role: "object" | "subject" | "ergative" | "possesor",
opts: T.TextOptions,
is2ndPersonPicker?: boolean,
}) {

View File

@ -106,6 +106,7 @@ export function makeNounSelection(entry: T.NounEntry, dynamicComplement?: true):
number,
numberCanChange: number === "singular",
adjectives: [],
possesor: undefined,
dynamicComplement,
};
}

View File

@ -45,7 +45,7 @@ export function VPExplorer(props: {
}) {
const [vps, setVps] = useStickyState<T.VPSelectionState>(
savedVps => makeVPSelectionState(props.verb, savedVps),
"vpsState5",
"vpsState6",
);
const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">(
savedMode => {
@ -97,6 +97,12 @@ export function VPExplorer(props: {
if (vps.verb?.isCompound === "dynamic") return;
setVps(switchSubjObj)
}
function handleShrinkPossesive(shrunkenPossesive: number | undefined) {
setVps(o => ({
...o,
shrunkenPossesive,
}));
}
function quizLock<T>(f: T) {
if (mode === "quiz") {
return () => {
@ -142,10 +148,12 @@ export function VPExplorer(props: {
? "ergative"
: "subject"
}
shrunkenPossesiveInPhrase={vps.shrunkenPossesive}
is2ndPersonPicker={vps.verb.tenseCategory === "imperative"}
np={vps.subject}
counterPart={vps.verb ? vps.verb.object : undefined}
onChange={handleSubjectChange}
handleShrinkPossesive={handleShrinkPossesive}
opts={props.opts}
/>
</div>
@ -153,6 +161,8 @@ export function VPExplorer(props: {
{(typeof vps.verb.object === "number")
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
: <NPPicker
shrunkenPossesiveInPhrase={vps.shrunkenPossesive}
handleShrinkPossesive={handleShrinkPossesive}
heading={roles.king === "object"
? <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "king", item: "object" })}>Object {roleIcon.king}</div>
: <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "servant", item: "object" })}>Object {roleIcon.servant}</div>}

View File

@ -445,6 +445,7 @@ function getRandomVPSelection(mix: MixType = "both") {
return {
subject: subject !== undefined ? subject : randSubj,
verb: randomizeTense(verb, true),
shrunkenPossesive: undefined,
}
}
const v: T.VerbSelectionComplete = {
@ -460,6 +461,7 @@ function getRandomVPSelection(mix: MixType = "both") {
return {
subject: randSubj,
verb: randomizeTense(v, true),
shrunkenPossesive: undefined,
};
};
};

View File

@ -95,5 +95,6 @@ export function makeVPSelectionState(
},
} : {},
},
shrunkenPossesive: os ? os.shrunkenPossesive : undefined,
};
}

View File

@ -162,41 +162,8 @@ export function randFromArray<M>(arr: M[]): M {
];
}
// TODO: deprecate this because we have it in np-tools?
/**
* Sees if a possiblePerson (for subject/object) is possible, given the other person
*
* @param possiblePerson
* @param existingPerson
*/
export function personIsAllowed(possiblePerson: T.Person, existingPerson?: T.Person): boolean {
const isFirstPerson = (p: T.Person) => [0, 1, 6, 7].includes(p);
const isSecondPerson = (p: T.Person) => [2, 3, 8, 9].includes(p);
// can't have both subject and object be 1st person
if (isFirstPerson(possiblePerson) && (existingPerson && isFirstPerson(existingPerson))) {
return false;
}
// can't have both subject and object be 2nd person
if (isSecondPerson(possiblePerson) && (existingPerson && isSecondPerson(existingPerson))) {
return false;
}
// otherwise it's ok
return true;
}
// TODO: deprecate this because we have it in np-tools?
/**
* Picks a random person while assuring that the other person is not in conflict
*
* @param other
*/
export function randomPerson(other?: T.Person): T.Person {
let newPerson: T.Person;
do {
newPerson = randomNumber(0, 12);
} while(!personIsAllowed(newPerson, other));
return newPerson;
}
export const isFirstPerson = (p: T.Person) => [0, 1, 6, 7].includes(p);
export const isSecondPerson = (p: T.Person) => [2, 3, 8, 9].includes(p);
export function incrementPerson(p: T.Person): T.Person {
return (p + 1) % 12;

View File

@ -1,5 +1,6 @@
import * as T from "../types";
import { parseEc } from "../lib/misc-helpers";
import { isFirstPerson, parseEc } from "../lib/misc-helpers";
import { isSecondPerson } from "./phrase-building/vp-tools";
function getRandPers(): T.Person {
return Math.floor(Math.random() * 12);
@ -31,22 +32,10 @@ export function randomPerson(a?: { prev?: T.Person, counterPart?: T.VerbObject |
}
export function isInvalidSubjObjCombo(subj: T.Person, obj: T.Person): boolean {
const firstPeople = [
T.Person.FirstSingMale,
T.Person.FirstSingFemale,
T.Person.FirstPlurMale,
T.Person.FirstPlurFemale,
];
const secondPeople = [
T.Person.SecondSingMale,
T.Person.SecondSingFemale,
T.Person.SecondPlurMale,
T.Person.SecondPlurFemale,
];
return (
(firstPeople.includes(subj) && firstPeople.includes(obj))
(isFirstPerson(subj) && isFirstPerson(obj))
||
(secondPeople.includes(subj) && secondPeople.includes(obj))
(isSecondPerson(subj) && isSecondPerson(obj))
);
}

View File

@ -12,6 +12,11 @@ import {
} from "./segment";
import { removeAccents } from "../accent-helpers";
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
import {
orderKidsSection,
findPossesiveToShrink,
shrinkNP,
} from "./compile-tools";
export function compileEP(EP: T.EPRendered, form: T.FormVersion): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string[] };
export function compileEP(EP: T.EPRendered, form: T.FormVersion, combineLengths: true): { ps: T.PsString[], e?: string[] };
@ -37,12 +42,20 @@ function getSegmentsAndKids(EP: T.EPRendered, form: T.FormVersion): { kids: Segm
}
return [s];
}
const subject = makeSegment(getPashtoFromRendered(EP.subject));
const predicate = makeSegment(getPashtoFromRendered(EP.predicate));
const possToShrink = findPossesiveToShrink(EP);
const shrunkenPossAllowed = !(form.removeKing && possToShrink?.role === "king");
const possUid = shrunkenPossAllowed ? EP.shrunkenPossesive : undefined;
const subject = makeSegment(getPashtoFromRendered(EP.subject, possUid, false));
const predicate = makeSegment(getPashtoFromRendered(EP.predicate, possUid, false));
return {
kids: EP.equative.hasBa
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])]
: [],
kids: orderKidsSection([
...EP.equative.hasBa
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])]
: [],
...(possToShrink && shrunkenPossAllowed)
? [shrinkNP(possToShrink)]
: [],
]),
NPs: [
...ifNotRemoved(subject, "subject"),
...ifNotRemoved(predicate, "predicate"),

View File

@ -0,0 +1,63 @@
import * as T from "../../types";
import {
Segment,
makeSegment,
} from "./segment";
import { getVerbBlockPosFromPerson } from "../misc-helpers";
import { pronouns } from "../grammar-units";
export function orderKidsSection(kids: Segment[]): Segment[] {
const sorted = [...kids];
return sorted.sort((a, b) => {
// ba first
if (a.isBa) return -1;
// kinds lined up 1st 2nd 3rd person
if (a.isMiniPronoun && b.isMiniPronoun) {
if (a.isMiniPronoun < b.isMiniPronoun) {
return -1;
}
if (a.isMiniPronoun > b.isMiniPronoun) {
return 1;
}
// TODO: is this enough?
return 0;
}
return 0;
});
}
export function findPossesiveToShrink(VP: T.VPRendered | T.EPRendered): T.Rendered<T.NPSelection> | undefined {
const uid = VP.shrunkenPossesive;
function findPossesiveInNP(NP: T.Rendered<T.NPSelection> | T.ObjectNP | undefined): T.Rendered<T.NPSelection> | undefined {
if (NP === undefined) return undefined;
if (typeof NP !== "object") return undefined;
if (!NP.possesor) return undefined;
if (NP.possesor.uid === uid) {
return NP.possesor.np;
}
return findPossesiveInNP(NP.possesor.np);
}
if (uid === undefined) return undefined;
const objPred: T.Rendered<T.NPSelection> | undefined = ("object" in VP)
? (typeof VP.object === "object" ? VP.object : undefined)
: (VP.predicate.type === "noun" || VP.predicate.type === "participle" || VP.predicate.type === "pronoun")
// typescript is dumb here;
? VP.predicate as T.Rendered<T.NPSelection>
: undefined;
return (
findPossesiveInNP(VP.subject)
||
findPossesiveInNP(objPred)
);
}
export function shrinkNP(np: T.Rendered<T.NPSelection>): Segment {
function getFirstSecThird(): 1 | 2 | 3 {
if ([0, 1, 6, 7].includes(np.person)) return 1;
if ([2, 3, 8, 9].includes(np.person)) return 2;
return 3;
}
const [row, col] = getVerbBlockPosFromPerson(np.person);
return makeSegment(pronouns.mini[row][col], ["isKid", getFirstSecThird()]);
}

View File

@ -1,6 +1,6 @@
import * as T from "../../types";
import {
concatPsString,
concatPsString, psStringEquals,
} from "../p-text-helpers";
import {
Segment,
@ -13,7 +13,6 @@ import {
import {
removeAccents,
} from "../accent-helpers";
import { getVerbBlockPosFromPerson } from "../misc-helpers";
import * as grammarUnits from "../grammar-units";
import {
removeBa,
@ -21,6 +20,11 @@ import {
} from "./vp-tools";
import { isImperativeTense, isModalTense, isPerfectTense } from "../type-predicates";
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
import {
orderKidsSection,
findPossesiveToShrink,
shrinkNP,
} from "./compile-tools";
type Form = T.FormVersion & { OSV?: boolean };
export function compileVP(VP: T.VPRendered, form: Form): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string [] };
@ -79,20 +83,33 @@ function compilePs({ NPs, kids, verb: { head, rest }, VP }: CompilePsInput): T.S
}
function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NPs: Segment[][] } {
const SO = {
subject: getPashtoFromRendered(VP.subject),
object: typeof VP.object === "object" ? getPashtoFromRendered(VP.object) : undefined,
}
const removeKing = form.removeKing && !(VP.isCompound === "dynamic" && VP.isPast);
const shrinkServant = form.shrinkServant && !(VP.isCompound === "dynamic" && !VP.isPast);
const toShrink = (() => {
const toShrinkServant = (() => {
if (!shrinkServant) return undefined;
if (!VP.servant) return undefined;
const servant = VP[VP.servant];
if (typeof servant !== "object") return undefined;
return servant;
})();
const shrunkenServant = toShrinkServant ? shrinkNP(toShrinkServant) : undefined;
const possToShrink = findPossesiveToShrink(VP);
const shrunkenPossesive = possToShrink ? shrinkNP(possToShrink) : undefined;
const shrunkenPossAllowed = possToShrink && shrunkenPossesive && (
!shrunkenServant || !psStringEquals(shrunkenPossesive.ps[0], shrunkenServant.ps[0])
) && (
// can only shrink the possesive if the parent of the possesive is still in full form
!(possToShrink.role === "king" && removeKing)
&&
!(possToShrink.role === "servant" && shrinkServant)
);
const shrinkPossUid = shrunkenPossAllowed
? VP.shrunkenPossesive
: undefined;
const SO = {
subject: getPashtoFromRendered(VP.subject, shrinkPossUid, false),
object: typeof VP.object === "object" ? getPashtoFromRendered(VP.object, shrinkPossUid, VP.subject.person) : undefined,
};
function getSegment(t: "subject" | "object"): Segment | undefined {
const word = (VP.servant === t)
? (!shrinkServant ? SO[t] : undefined)
@ -106,12 +123,14 @@ function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NP
const object = getSegment("object");
return {
kids: [
kids: orderKidsSection([
...VP.verb.hasBa
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] : [],
...toShrink
? [shrinkNP(toShrink)] : [],
],
...shrunkenServant
? [shrunkenServant] : [],
...(shrunkenPossesive && shrunkenPossAllowed)
? [shrunkenPossesive] : [],
]),
NPs: [
[
...subject ? [subject] : [],
@ -236,11 +255,6 @@ function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[
];
}
function shrinkNP(np: T.Rendered<T.NPSelection>): Segment {
const [row, col] = getVerbBlockPosFromPerson(np.person);
return makeSegment(grammarUnits.pronouns.mini[row][col], ["isKid", "isMiniPronoun"]);
}
function mergeSegments(s1: Segment, s2: Segment, noSpace?: "no space"): Segment {
if (noSpace) {
return s2.adjust({ ps: (p) => concatPsString(s1.ps[0], p) });
@ -295,4 +309,3 @@ function compileEnglish(VP: T.VPRendered): string[] | undefined {
}))
: undefined;
}

View File

@ -1,7 +1,11 @@
import {
isFirstPerson,
isSecondPerson,
} from "../../lib/misc-helpers";
import * as T from "../../types";
import { concatPsString } from "../p-text-helpers";
export function getPashtoFromRendered(np: T.Rendered<T.NPSelection | T.EqCompSelection>): T.PsString[] {
function getBaseAndAdjectives(np: T.Rendered<T.NPSelection | T.EqCompSelection>): T.PsString[] {
const adjs = np.adjectives;
if (!adjs) {
return np.ps;
@ -18,25 +22,106 @@ export function getPashtoFromRendered(np: T.Rendered<T.NPSelection | T.EqCompSel
));
}
export function getEnglishFromRendered(np: T.Rendered<T.NPSelection | T.EqCompSelection>): string | undefined {
if (!np.e) return undefined;
if (np.type === "noun") {
try {
// split out the atricles so adjectives can be stuck inbetween them and the word
const chunks = np.e.split("the)");
const [articles, word] = chunks.length === 1
? ["", np.e]
: [chunks[0] + "the) ", chunks[1]];
const adjs = !np.adjectives
? ""
: np.adjectives.reduce((accum, curr): string => {
if (!curr.e) throw new Error("no english for adjective");
return accum + curr.e + " ";
}, "");
return `${articles}${adjs}${word}`;
} catch (e) {
return undefined;
}
export function getPashtoFromRendered(np: T.Rendered<T.NPSelection | T.EqCompSelection>, shrunkenPossesive: number | undefined, subjectsPerson: false | T.Person): T.PsString[] {
const base = getBaseAndAdjectives(np);
if (!np.possesor || np.possesor.uid === shrunkenPossesive) {
return base;
}
return np.e;
return addPossesor(np.possesor.np, base, subjectsPerson);
}
function addPossesor(owner: T.Rendered<T.NPSelection>, existing: T.PsString[], subjectsPerson: false | T.Person): T.PsString[] {
function willBeReflexive(subj: T.Person, obj: T.Person): boolean {
return (
([0, 1].includes(subj) && [0, 1].includes(obj))
||
([2, 3].includes(subj) && [8, 9].includes(obj))
);
}
const wPossesor = existing.flatMap(ps => (
getBaseAndAdjectives(owner).map(v => (
(owner.type === "pronoun" && subjectsPerson !== false && willBeReflexive(subjectsPerson, owner.person))
? concatPsString({ p: "خپل", f: "khpul" }, " ", ps)
: (owner.type === "pronoun" && isFirstPerson(owner.person))
? concatPsString({ p: "ز", f: "z" }, v, " ", ps)
: (owner.type === "pronoun" && isSecondPerson(owner.person))
? concatPsString({ p: "س", f: "s" }, v, " ", ps)
: concatPsString({ p: "د", f: "du" }, " ", v, " ", ps)
))
));
if (!owner.possesor) {
return wPossesor;
}
return addPossesor(owner.possesor.np, wPossesor, subjectsPerson);
}
function addArticlesAndAdjs(np: T.Rendered<T.NounSelection>): string | undefined {
if (!np.e) return undefined;
try {
// split out the atricles so adjectives can be stuck inbetween them and the word
const chunks = np.e.split("the)");
const [articles, word] = chunks.length === 1
? ["", np.e]
: [chunks[0] + "the) ", chunks[1]];
const adjs = !np.adjectives
? ""
: np.adjectives.reduce((accum, curr): string => {
if (!curr.e) throw new Error("no english for adjective");
return accum + curr.e + " ";
}, "");
return `${articles}${adjs}${word}`;
} catch (e) {
return undefined;
}
}
function addPossesors(possesor: T.Rendered<T.NPSelection> | undefined, base: string | undefined): string | undefined {
function removeArticles(s: string): string {
return s.replace("(the) ", "").replace("(a/the) ", "");
}
if (!base) return undefined;
if (!possesor) return base;
if (possesor.type === "pronoun") {
return `${pronounPossEng(possesor.person)} ${removeArticles(base)}`;
}
const possesorE = getEnglishFromRendered(possesor);
if (!possesorE) return undefined;
return `${possesorE}'s ${removeArticles(base)}`;
}
function pronounPossEng(p: T.Person): string {
if (p === T.Person.FirstSingMale || p === T.Person.FirstSingFemale) {
return "my";
}
if (p === T.Person.FirstPlurMale || p === T.Person.FirstPlurFemale) {
return "our";
}
if (p === T.Person.SecondSingMale || p === T.Person.SecondSingFemale) {
return "your";
}
if (p === T.Person.SecondPlurMale || p === T.Person.SecondPlurFemale) {
return "your (pl.)";
}
if (p === T.Person.ThirdSingMale) {
return "his/its";
}
if (p === T.Person.ThirdSingFemale) {
return "her/its";
}
return "their";
}
export function getEnglishFromRendered(r: T.Rendered<T.NPSelection | T.EqCompSelection>): string | undefined {
if (!r.e) return undefined;
if (r.type === "loc. adv." || r.type === "adjective") {
return r.e;
}
if (r.type === "noun") {
// TODO: shouldn't have to do this 'as' - should be automatically narrowing
const np = r as T.Rendered<T.NounSelection>;
return addPossesors(np.possesor?.np, addArticlesAndAdjs(np));
}
// TODO: possesives in English for participles and pronouns too!
return r.e;
}

View File

@ -17,7 +17,7 @@ function chooseInflection(inflections: T.UnisexSet<T.InflectionSet>, pers: T.Per
return inflections[gender][infNumber];
}
export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Person, inflected: boolean): T.Rendered<T.AdjectiveSelection> {
export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Person, inflected: boolean, role: "king" | "servant" | "none"): T.Rendered<T.AdjectiveSelection> {
const infs = inflectWord(a.entry);
const eWord = getEnglishWord(a.entry);
const e = !eWord
@ -31,6 +31,7 @@ export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Pers
ps: [psStringFromEntry(a.entry)],
e,
inflected: false,
role,
person,
}
if (!infs.inflections || !isUnisexSet(infs.inflections)) {
@ -42,6 +43,7 @@ export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Pers
ps: chooseInflection(infs.inflections, person, inflected),
e,
inflected: false,
role,
person,
};
}

View File

@ -8,12 +8,17 @@ import { getPersonFromVerbForm } from "../../lib/misc-helpers";
import { getVerbBlockPosFromPerson } from "../misc-helpers";
import { getEnglishWord } from "../get-english-word";
import { psStringFromEntry } from "../p-text-helpers";
import { personGender, personIsPlural } from "../../library";
import { isLocativeAdverbEntry } from "../type-predicates";
import { renderAdjectiveSelection } from "./render-adj";
export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
const kingPerson = (EP.subject.type === "pronoun")
const king = (EP.subject.type === "pronoun")
? "subject"
: EP.predicate.type === "NP"
? "predicate"
: "subject";
// TODO: less repetative logic
const kingPerson = king === "subject"
? getPersonFromNP(EP.subject)
: EP.predicate.type === "NP"
? getPersonFromNP(EP.predicate.selection)
@ -21,12 +26,13 @@ export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
return {
type: "EPRendered",
king: EP.predicate.type === "Complement" ? "subject" : "predicate",
subject: renderNPSelection(EP.subject, false, false, "subject"),
subject: renderNPSelection(EP.subject, false, false, "subject", king === "subject" ? "king" : "none"),
predicate: EP.predicate.type === "NP"
? renderNPSelection(EP.predicate.selection, false, true, "subject")
? renderNPSelection(EP.predicate.selection, false, true, "subject", "king")
: renderEqCompSelection(EP.predicate.selection, kingPerson),
equative: renderEquative(EP.equative, kingPerson),
englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative),
shrunkenPossesive: EP.shrunkenPossesive,
};
}
@ -68,10 +74,11 @@ function renderEqCompSelection(s: T.EqCompSelection, person: T.Person): T.Render
e,
inflected: false,
person,
role: "none",
};
}
if (s.type === "adjective") {
return renderAdjectiveSelection(s, person, false)
return renderAdjectiveSelection(s, person, false, "none")
}
throw new Error("invalid EqCompSelection");
}
@ -136,9 +143,9 @@ function getEnglishConj(p: T.Person, e: string | T.EnglishBlock): string {
return e[row][col];
}
function chooseInflection(inflections: T.UnisexSet<T.InflectionSet>, pers: T.Person): T.ArrayOneOrMore<T.PsString> {
const gender = personGender(pers);
const plural = personIsPlural(pers);
return inflections[gender][plural ? 1 : 0];
}
// function chooseInflection(inflections: T.UnisexSet<T.InflectionSet>, pers: T.Person): T.ArrayOneOrMore<T.PsString> {
// const gender = personGender(pers);
// const plural = personIsPlural(pers);
// return inflections[gender][plural ? 1 : 0];
// }

View File

@ -12,10 +12,11 @@ import {
import { parseEc } from "../misc-helpers";
import { getEnglishWord } from "../get-english-word";
import { renderAdjectiveSelection } from "./render-adj";
import { isPattern5Entry, isUnisexNounEntry } from "../type-predicates";
export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflectEnglish: boolean, role: "subject"): T.Rendered<T.NPSelection>
export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "object"): T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale | "none";
export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "subject" | "object"): T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale | "none" {
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" {
if (typeof NP !== "object") {
if (role !== "object") {
throw new Error("ObjectNP only allowed for objects");
@ -23,18 +24,18 @@ export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boo
return NP;
}
if (NP.type === "noun") {
return renderNounSelection(NP, inflected);
return renderNounSelection(NP, inflected, soRole);
}
if (NP.type === "pronoun") {
return renderPronounSelection(NP, inflected, inflectEnglish);
return renderPronounSelection(NP, inflected, inflectEnglish, soRole);
}
if (NP.type === "participle") {
return renderParticipleSelection(NP, inflected)
return renderParticipleSelection(NP, inflected, soRole)
}
throw new Error("unknown NP type");
};
function renderNounSelection(n: T.NounSelection, inflected: boolean): T.Rendered<T.NounSelection> {
function renderNounSelection(n: T.NounSelection, inflected: boolean, role: "servant" | "king" | "none"): T.Rendered<T.NounSelection> {
const english = getEnglishFromNoun(n.entry, n.number);
const pashto = ((): T.PsString[] => {
const infs = inflectWord(n.entry);
@ -52,32 +53,51 @@ function renderNounSelection(n: T.NounSelection, inflected: boolean): T.Rendered
const person = getPersonNumber(n.gender, n.number);
return {
...n,
adjectives: n.adjectives.map(a => renderAdjectiveSelection(a, person, inflected)),
adjectives: n.adjectives.map(a => renderAdjectiveSelection(a, person, inflected, role)),
person,
inflected,
role,
ps: pashto,
e: english,
possesor: renderPossesor(n.possesor, role),
};
}
function renderPronounSelection(p: T.PronounSelection, inflected: boolean, englishInflected: boolean): T.Rendered<T.PronounSelection> {
function renderPronounSelection(p: T.PronounSelection, inflected: boolean, englishInflected: boolean, role: "servant" | "king" | "none"): T.Rendered<T.PronounSelection> {
const [row, col] = getVerbBlockPosFromPerson(p.person);
return {
...p,
inflected,
role,
ps: grammarUnits.pronouns[p.distance][inflected ? "inflected" : "plain"][row][col],
e: grammarUnits.persons[p.person].label[englishInflected ? "object" : "subject"],
};
}
function renderParticipleSelection(p: T.ParticipleSelection, inflected: boolean): T.Rendered<T.ParticipleSelection> {
function renderParticipleSelection(p: T.ParticipleSelection, inflected: boolean, role: "servant" | "king" | "none"): T.Rendered<T.ParticipleSelection> {
return {
...p,
inflected,
role,
person: T.Person.ThirdPlurMale,
// TODO: More robust inflection of inflecting pariticiples - get from the conjugation engine
ps: [psStringFromEntry(p.verb.entry)].map(ps => inflected ? concatPsString(ps, { p: "و", f: "o" }) : ps),
e: getEnglishParticiple(p.verb.entry),
possesor: renderPossesor(p.possesor, role),
};
}
function renderPossesor(possesor: { np: T.NPSelection, uid: number } | undefined, possesorRole: "servant" | "king" | "none"): { np: T.Rendered<T.NPSelection>, uid: number } | undefined {
if (!possesor) return undefined;
return {
uid: possesor.uid,
np: renderNPSelection(
possesor.np,
!(possesor.np.type === "noun" && isUnisexNounEntry(possesor.np.entry) && isPattern5Entry(possesor.np.entry)),
false,
"subject",
possesorRole,
),
};
}

View File

@ -47,11 +47,12 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
type: "VPRendered",
king,
servant,
shrunkenPossesive: VP.shrunkenPossesive,
isPast,
isTransitive,
isCompound: VP.verb.isCompound,
subject: renderNPSelection(VP.subject, inflectSubject, false, "subject"),
object: renderNPSelection(VP.verb.object, inflectObject, true, "object"),
subject: renderNPSelection(VP.subject, inflectSubject, false, "subject", king === "subject" ? "king" : "servant"),
object: renderNPSelection(VP.verb.object, inflectObject, true, "object", king === "object" ? "king" : "servant"),
verb: renderVerbSelection(VP.verb, kingPerson, objectPerson),
englishBase: renderEnglishVPBase({
subjectPerson,

View File

@ -12,7 +12,7 @@ type SegmentDescriptions = {
isVerbHead?: boolean,
isOoOrWaaHead?: boolean,
isVerbRest?: boolean,
isMiniPronoun?: boolean,
isMiniPronoun?: number,
isKid?: boolean,
// TODO: Simplify to just isKidAfterHead?
isKidBetweenHeadAndRest?: boolean,
@ -25,28 +25,23 @@ export type Segment = { ps: T.PsString[] } & SegmentDescriptions & {
adjust: (o: { ps?: T.PsString | T.PsString[] | ((ps: T.PsString) => T.PsString), desc?: SDT[] }) => Segment,
};
function addAdjectives(ps: T.PsString[], adjectives?: T.Rendered<T.AdjectiveSelection>[]): T.PsString[] {
if (!adjectives) return ps;
return ps.map(p => {
// TODO: more thorough use of all possible variends?
return concatPsString(...adjectives.map(a => a.ps[0]), p);
});
}
export function makeSegment(
input: T.Rendered<T.NounSelection> | T.PsString | T.PsString[],
options?: (keyof SegmentDescriptions)[],
input: T.PsString | T.PsString[],
options?: (keyof SegmentDescriptions | 1 | 2 | 3)[],
): Segment {
const ps: T.PsString[] = Array.isArray(input)
? input
: "type" in input
? addAdjectives(input.ps, input.adjectives)
: [input];
return {
ps: Array.isArray(ps) ? ps : [ps],
...options && options.reduce((all, curr) => ({
...all,
[curr]: true,
...typeof curr === "number" ? {
isMiniPronoun: curr,
} : {
[curr]: true,
},
}), {}),
adjust: function(o): Segment {
return {

View File

@ -501,6 +501,7 @@ export type VPRendered = {
type: "VPRendered",
king: "subject" | "object",
servant: "subject" | "object" | undefined,
shrunkenPossesive: number | undefined,
isPast: boolean,
isTransitive: boolean,
isCompound: "stative" | "dynamic" | false,
@ -529,11 +530,13 @@ export type Tense = EquativeTense | VerbTense | PerfectTense | ModalTense | Impe
export type VPSelectionState = {
subject: NPSelection | undefined,
verb: VerbSelection,
shrunkenPossesive: undefined | number,
};
export type VPSelectionComplete = {
subject: NPSelection,
verb: VerbSelectionComplete,
shrunkenPossesive: undefined | number,
};
export type VerbSelectionComplete = Omit<VerbSelection, "object" | "verbTense" | "perfectTense" | "imperfectiveTense" | "tenseCategory"> & {
@ -587,6 +590,11 @@ export type NPType = "noun" | "pronoun" | "participle";
export type ObjectNP = "none" | Person.ThirdPlurMale;
export type PossesorSelection = {
uid: number,
np: NPSelection,
}
// TODO require/import Person and PsString
export type NounSelection = {
type: "noun",
@ -597,6 +605,7 @@ export type NounSelection = {
numberCanChange: boolean,
dynamicComplement?: boolean,
adjectives: AdjectiveSelection[],
possesor: undefined | PossesorSelection,
};
export type AdjectiveSelection = {
@ -619,6 +628,7 @@ export type PronounSelection = {
export type ParticipleSelection = {
type: "participle",
verb: VerbEntry,
possesor: undefined | PossesorSelection,
};
// not object
@ -629,7 +639,7 @@ export type ReplaceKey<T, K extends string, R> = T extends Record<K, unknown> ?
export type FormVersion = { removeKing: boolean, shrinkServant: boolean };
export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelection> = ReplaceKey<
Omit<T, "changeGender" | "changeNumber" | "changeDistance" | "adjectives">,
Omit<T, "changeGender" | "changeNumber" | "changeDistance" | "adjectives" | "possesor">,
"e",
string
> & {
@ -637,8 +647,13 @@ export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelectio
e?: string,
inflected: boolean,
person: Person,
role: "king" | "servant" | "none",
// TODO: better recursive thing
adjectives?: Rendered<AdjectiveSelection>[],
possesor?: {
uid: number,
np: Rendered<NPSelection>,
},
};
// TODO: recursive changing this down into the possesor etc.
@ -650,6 +665,7 @@ export type EPSelectionState = {
Complement: EqCompSelection | undefined,
},
equative: EquativeSelection,
shrunkenPossesive: undefined | number,
};
export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"> & {
@ -684,6 +700,7 @@ export type EPRendered = {
predicate: Rendered<NPSelection | EqCompSelection>,
equative: EquativeRendered,
englishBase?: string[],
shrunkenPossesive: undefined | number,
}
export type EntryFeeder = {