rough sandwich working

This commit is contained in:
lingdocs 2022-05-20 18:17:03 -05:00
parent 92b80c3b6a
commit e8ec806ecb
19 changed files with 552 additions and 154 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@lingdocs/pashto-inflector",
"version": "2.5.5",
"version": "2.5.6",
"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",
@ -76,7 +76,10 @@
"extends": [
"react-app",
"react-app/jest"
]
],
"rules": {
"no-warning-comments": [1, {"terms": ["fixme", "xxx"], "location": "anywhere"}]
}
},
"browserslist": {
"production": [

View File

@ -111,4 +111,69 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
</div>
}
export function SandwichSelect<E extends T.Sandwich>(props: {
sandwiches: E[],
value: E | undefined,
onChange: (value: E | undefined) => void,
name: string | undefined,
opts: T.TextOptions,
style?: StyleHTMLAttributes<HTMLDivElement>,
}) {
const divStyle = props.style || { width: "13rem" };
const placeholder = "Select Sandwich…";
const value = props.value ? makeSandwichOption(props.value) : undefined;
const options = props.sandwiches
// .sort((a, b) => {
// if ("entry" in a) {
// return a.before.p.localeCompare("p" in b ? b.p : b.entry.p, "af-PS")
// }
// return a.p.localeCompare("p" in b ? b.p : b.entry.p, "af-PS");
// })
.map(makeSandwichOption);
const onChange = (v: { label: string | JSX.Element, value: string } | null) => {
if (!v) {
props.onChange(undefined);
return;
}
const s = props.sandwiches.find(e => {
const sValue = JSON.parse(v.value) as T.Sandwich;
if (sValue.type !== "sandwich") throw new Error("error converting selected sandwich value to a sandwich");
return sandwichSideEq(e.before, sValue.before)
&& sandwichSideEq(e.after, sValue.after)
&& (e.e === sValue.e);
});
if (!s) return;
props.onChange(s);
}
return <div style={divStyle}>
<Select
styles={customStyles}
isSearchable={true}
value={value}
// @ts-ignore - gets messed up when using customStyles
onChange={onChange}
className="mb-2"
options={options}
placeholder={placeholder}
/>
</div>
}
function sandwichSideEq(s1: T.PsString | undefined, s2: T.PsString | undefined): boolean {
if (s1 === undefined && s2 === undefined) {
return true
}
if (typeof s1 === "object" && typeof s2 === "object" && s1.p === s2.p) {
return true;
}
return false;
}
function makeSandwichOption(s: T.Sandwich): { label: string, value: string } {
return {
label: `${s.before ? s.before.p : ""} ... ${s.after ? s.after.p : ""} (${s.e})`,
value: JSON.stringify(s),
};
}
export default EntrySelect;

View File

@ -2,7 +2,8 @@ import { useState, useEffect } from "react";
import * as T from "../../../types";
import AdjectivePicker from "../../np-picker/AdjectivePicker";
import LocativeAdverbPicker from "./LocativeAdverbPicker";
const compTypes: T.EqCompType[] = ["adjective", "loc. adv."];
import SandwichPicker from "../../np-picker/SandwichPicker";
const compTypes: T.EqCompType[] = ["adjective", "loc. adv.", "sandwich"];
function EqCompPicker(props: {
onChange: (comp: T.EqCompSelection | undefined) => void,
@ -12,9 +13,13 @@ function EqCompPicker(props: {
heading?: JSX.Element | string,
entryFeeder: T.EntryFeeder,
}) {
const [compType, setCompType] = useState<T.EqCompType | undefined>(props.comp ? props.comp.type : undefined);
const [compType, setCompType] = useState<T.EqCompType | undefined>(props.comp
? props.comp.type
: undefined);
useEffect(() => {
setCompType(props.comp ? props.comp.type : undefined);
setCompType(props.comp
? props.comp.type
: undefined);
}, [props.comp]);
function handleClear() {
setCompType(undefined);
@ -69,6 +74,15 @@ function EqCompPicker(props: {
opts={props.opts}
onChange={props.onChange}
/>
: compType === "sandwich"
? <SandwichPicker
onChange={props.onChange}
opts={props.opts}
sandwich={props.comp?.type === "sandwich" ? props.comp : undefined}
entryFeeder={props.entryFeeder}
// TODO: get phraseIsComplete working here
phraseIsComplete={false}
/>
: null}
</div>
</>;

View File

@ -137,7 +137,7 @@ function NPPronounPicker({ onChange, pronoun, role, opts, is2ndPersonPicker }: {
const pSpec = is2ndPersonPicker
? [pSpecA[1]]
: pSpecA;
return <div style={{ maxWidth: "145px", padding: 0 }}>
return <div style={{ maxWidth: "145px", padding: 0, margin: "0 auto" }}>
<div className="d-flex flex-row justify-content-between mb-2">
<ButtonSelect
xSmall

View File

@ -0,0 +1,62 @@
import * as T from "../../types";
import { sandwiches } from "../../lib/sandwiches";
import { SandwichSelect } from "../../components/EntrySelect";
import { useState } from "react";
import NPPicker from "./NPPicker";
function SandwichPicker(props: {
opts: T.TextOptions,
onChange: (s: T.SandwichSelection<T.Sandwich> | undefined) => void,
sandwich: T.SandwichSelection<T.Sandwich> | undefined;
entryFeeder: T.EntryFeeder,
phraseIsComplete: boolean,
}) {
const [sandwichBase, setSandwichBase] = useState<T.Sandwich | undefined>(undefined);
function handleNounChange(n: T.NPSelection | undefined) {
if (!n) {
props.onChange(undefined);
return;
}
if (!sandwichBase) return;
props.onChange({
...sandwichBase,
inside: n,
});
}
function handleSandwichChange(s: T.Sandwich | undefined) {
if (!s) {
setSandwichBase(undefined);
props.onChange(undefined);
return;
}
setSandwichBase(s);
if (!props.sandwich) return;
props.onChange({
...props.sandwich,
...s,
});
}
return <div>
{sandwichBase && <div className="mb-2" style={{ margin: "0 auto" }}>
<NPPicker
onChange={handleNounChange}
np={props.sandwich ? props.sandwich.inside : undefined}
counterPart={undefined}
opts={props.opts}
role="object"
cantClear={true}
entryFeeder={props.entryFeeder}
phraseIsComplete={props.phraseIsComplete}
/>
</div>}
<SandwichSelect
name="sandwich"
opts={props.opts}
sandwiches={sandwiches}
value={sandwichBase}
onChange={handleSandwichChange}
/>
</div>;
}
export default SandwichPicker;

View File

@ -13,7 +13,7 @@ function VPDisplay({ VP, opts, setForm }: {
if (!("type" in VP)) {
return <div className="lead text-muted text-center mt-4">
{(() => {
const twoNPs = (VP.subject === undefined) && (VP.verb.object === undefined);
const twoNPs = (VP.subject === undefined) && (VP.object === undefined);
return `Choose NP${twoNPs ? "s " : ""} to make a phrase`;
})()}
</div>;

View File

@ -67,10 +67,9 @@ function VPExplorer(props: {
}, 1500);
}
useEffect(() => {
const newVps = makeVPSelectionState(props.verb, vps);
adjustVps({
type: "load vps",
payload: newVps,
type: "set verb",
payload: props.verb,
});
// eslint-disable-next-line
}, [props.verb]);
@ -161,7 +160,7 @@ function VPExplorer(props: {
{mode === "phrases" ? <i className="fas fa-share-alt" /> : ""}
</div>
</div>
{(vps.verb && (typeof vps.verb.object === "object") && (vps.verb.isCompound !== "dynamic") && (vps.verb.tenseCategory !== "imperative") &&(mode === "phrases")) &&
{(vps.verb && (typeof vps.object === "object") && (vps.verb.isCompound !== "dynamic") && (vps.verb.tenseCategory !== "imperative") &&(mode === "phrases")) &&
<div className="text-center my-2">
<button onClick={handleSubjObjSwap} className="btn btn-sm btn-light">
<i className="fas fa-exchange-alt mr-2" /> subj/obj
@ -192,14 +191,14 @@ function VPExplorer(props: {
}
is2ndPersonPicker={vps.verb.tenseCategory === "imperative"}
np={vps.subject}
counterPart={vps.verb ? vps.verb.object : undefined}
counterPart={vps.verb ? vps.object : undefined}
onChange={handleSubjectChange}
opts={props.opts}
isShrunk={(servantIsShrunk && roles.servant === "subject")}
/>
</div>
{vps.verb && (vps.verb.object !== "none") && <div className="my-2" style={{ background: (servantIsShrunk && roles.servant === "object") ? shrunkenBackground : "inherit" }}>
{(typeof vps.verb.object === "number")
{vps.verb && (vps.object !== "none") && <div className="my-2" style={{ background: (servantIsShrunk && roles.servant === "object") ? shrunkenBackground : "inherit" }}>
{(typeof vps.object === "number")
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
: <NPPicker
phraseIsComplete={phraseIsComplete}
@ -218,7 +217,7 @@ function VPExplorer(props: {
</div>}
entryFeeder={props.entryFeeder}
role="object"
np={vps.verb.object}
np={vps.object}
counterPart={vps.subject}
onChange={handleObjectChange}
opts={props.opts}

View File

@ -375,8 +375,8 @@ function completeVPs(vps: T.VPSelectionState): T.VPSelectionComplete {
const oldSubj = vps.subject?.type === "pronoun"
? vps.subject.person
: undefined;
const oldObj = (typeof vps.verb.object === "object" && vps.verb.object.type === "pronoun")
? vps.verb.object.person
const oldObj = (typeof vps.object === "object" && vps.object.type === "pronoun")
? vps.object.person
: undefined;
const { subj, obj } = randomSubjObj(
oldSubj === undefined
@ -389,17 +389,6 @@ function completeVPs(vps: T.VPSelectionState): T.VPSelectionComplete {
const t = getTenseFromVerbSelection(vps.verb);
const verb: T.VerbSelectionComplete = {
...vps.verb,
object: (
(typeof vps.verb.object === "object" && !(vps.verb.object.type === "noun" && vps.verb.object.dynamicComplement))
||
vps.verb.object === undefined
)
? {
type: "pronoun",
distance: "far",
person: obj,
}
: vps.verb.object,
tense: isImperativeTense(t) ? "presentVerb" : t,
};
return {
@ -409,18 +398,29 @@ function completeVPs(vps: T.VPSelectionState): T.VPSelectionComplete {
distance: "far",
person: subj,
},
object: (
(typeof vps.object === "object" && !(vps.object.type === "noun" && vps.object.dynamicComplement))
||
vps.object === undefined
)
? {
type: "pronoun",
distance: "far",
person: obj,
}
: vps.object,
verb,
};
}
function getRandomVPSelection(mix: MixType = "both") {
// TODO: Type safety to make sure it's safe?
return ({ subject, verb }: T.VPSelectionComplete): T.VPSelectionComplete => {
return ({ subject, verb, object }: T.VPSelectionComplete): T.VPSelectionComplete => {
const oldSubj = (subject.type === "pronoun")
? subject.person
: undefined;
const oldObj = (typeof verb.object === "object" && verb.object.type === "pronoun")
? verb.object.person
const oldObj = (typeof object === "object" && object.type === "pronoun")
? object.person
: undefined;
const { subj, obj } = randomSubjObj(
oldSubj !== undefined ? { subj: oldSubj, obj: oldObj } : undefined
@ -433,8 +433,8 @@ function getRandomVPSelection(mix: MixType = "both") {
distance: "far",
person: subj,
};
const randObj: T.PronounSelection = typeof verb?.object === "object" && verb.object.type === "pronoun" ? {
...verb.object,
const randObj: T.PronounSelection = typeof object === "object" && object.type === "pronoun" ? {
...object,
person: obj,
} : {
type: "pronoun",
@ -445,23 +445,21 @@ function getRandomVPSelection(mix: MixType = "both") {
if (mix === "tenses") {
return {
subject: subject !== undefined ? subject : randSubj,
object: object !== undefined ? object : randObj,
verb: randomizeTense(verb, true),
form: { removeKing: false, shrinkServant: false },
}
}
const v: T.VerbSelectionComplete = {
...verb,
object: (
(typeof verb.object === "object" && !(verb.object.type === "noun" && verb.object.dynamicComplement))
||
verb.object === undefined
)
? randObj
: verb.object,
};
return {
subject: randSubj,
verb: randomizeTense(v, true),
object: (
(typeof object === "object" && !(object.type === "noun" && object.dynamicComplement))
||
object === undefined
)
? randObj
: object,
verb: randomizeTense(verb, true),
form: { removeKing: false, shrinkServant: false },
};
};

View File

@ -15,12 +15,12 @@ export function makeVPSelectionState(
function getTransObjFromos() {
if (
!os ||
os.verb.object === "none" ||
typeof os.verb.object === "number" ||
os.object === "none" ||
typeof os.object === "number" ||
os.verb.isCompound === "dynamic" ||
(os.verb.object?.type === "noun" && os.verb.object.dynamicComplement)
(os.object?.type === "noun" && os.object.dynamicComplement)
) return undefined;
return os.verb.object;
return os.object;
}
const transitivity: T.Transitivity = "grammaticallyTransitive" in info
? "transitive"
@ -47,6 +47,7 @@ export function makeVPSelectionState(
: undefined;
return {
subject,
object,
verb: {
type: "verb",
verb: verb,
@ -55,7 +56,6 @@ export function makeVPSelectionState(
perfectTense: os ? os.verb.perfectTense : "presentPerfect",
imperativeTense: os ? os.verb.imperativeTense : "imperfectiveImperative",
tenseCategory: os ? os.verb.tenseCategory : "basic",
object,
transitivity,
isCompound,
voice: transitivity === "transitive"
@ -70,27 +70,33 @@ export function makeVPSelectionState(
};
}
export function changeStatDyn(v: T.VerbSelection, s: "dynamic" | "stative"): T.VerbSelection {
const info = getVerbInfo(v.verb.entry, v.verb.complement);
export function changeStatDyn(v: T.VPSelectionState, s: "dynamic" | "stative"): T.VPSelectionState {
const info = getVerbInfo(v.verb.verb.entry, v.verb.verb.complement);
if (!("stative" in info)) {
return v;
}
return {
...v,
isCompound: s,
object: s === "dynamic"
? makeNounSelection(info.dynamic.objComplement.entry as T.NounEntry, undefined, true)
: undefined,
dynAuxVerb: s === "dynamic"
? { entry: info.dynamic.auxVerb } as T.VerbEntry
: undefined,
verb: {
...v.verb,
isCompound: s,
dynAuxVerb: s === "dynamic"
? { entry: info.dynamic.auxVerb } as T.VerbEntry
: undefined,
},
};
}
export function changeTransitivity(v: T.VerbSelection, transitivity: "transitive" | "grammatically transitive"): T.VerbSelection {
export function changeTransitivity(v: T.VPSelectionState, transitivity: "transitive" | "grammatically transitive"): T.VPSelectionState {
return {
...v,
transitivity,
object: transitivity === "grammatically transitive" ? T.Person.ThirdPlurMale : undefined,
verb: {
...v.verb,
transitivity,
},
};
}

View File

@ -10,6 +10,9 @@ import {
isImperativeTense,
} from "../../lib/type-predicates";
import { checkForMiniPronounsError } from "../../lib/phrase-building/compile";
import {
makeVPSelectionState,
} from "./verb-selection";
export type VpsReducerAction = {
type: "load vps",
@ -48,7 +51,10 @@ export type VpsReducerAction = {
payload: "basic" | "modal" | "perfect" | "imperative",
} | {
type: "toggle servant shrink",
};
} | {
type: "set verb",
payload: T.VerbEntry,
}
export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, sendAlert?: (msg: string) => void): T.VPSelectionState {
return ensureMiniPronounsOk(vps, doReduce());
@ -70,7 +76,7 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
if (
!skipPronounConflictCheck
&&
hasPronounConflict(subject, vps.verb?.object)
hasPronounConflict(subject, vps.object)
) {
if (sendAlert) sendAlert("That combination of pronouns is not allowed");
return vps;
@ -82,7 +88,7 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
}
if (action.type === "set object") {
if (!vps.verb) return vps;
if ((vps.verb.object === "none") || (typeof vps.verb.object === "number")) {
if ((vps.object === "none") || (typeof vps.object === "number")) {
return vps;
}
const object = action.payload;
@ -93,10 +99,7 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
}
return {
...vps,
verb: {
...vps.verb,
object,
},
object,
};
}
if (action.type === "swap subj/obj") {
@ -118,10 +121,10 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
if (voice === "passive") {
return {
...vps,
subject: typeof vps.verb.object === "object" ? vps.verb.object : undefined,
subject: typeof vps.object === "object" ? vps.object : undefined,
object: "none",
verb: {
...vps.verb,
object: "none",
voice,
},
};
@ -129,10 +132,9 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
return {
...vps,
subject: undefined,
object: typeof vps.subject === "object" ? vps.subject : undefined,
verb: {
...vps.verb,
// TODO: is this ok??
object: typeof vps.subject === "object" ? vps.subject : undefined,
voice,
},
};
@ -143,17 +145,11 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
}
if (action.type === "set transitivity") {
if (!(vps.verb && vps.verb.canChangeTransitivity)) return vps;
return {
...vps,
verb: changeTransitivity(vps.verb, action.payload),
};
return changeTransitivity(vps, action.payload);
}
if (action.type === "set statDyn") {
if (!(vps.verb && vps.verb.canChangeStatDyn)) return vps;
return {
...vps,
verb: changeStatDyn(vps.verb, action.payload),
};
return changeStatDyn(vps, action.payload);
}
if (action.type === "set negativity") {
if (!vps.verb) return vps;
@ -220,14 +216,17 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
},
};
}
// if (action.type === "toggle servant shrink") {
return {
...vps,
form: {
...vps.form,
shrinkServant: !vps.form.shrinkServant,
},
};
if (action.type === "toggle servant shrink") {
return {
...vps,
form: {
...vps.form,
shrinkServant: !vps.form.shrinkServant,
},
};
}
// if (action.type === "set verb") {
return makeVPSelectionState(action.payload, vps);
// }
}
}

View File

@ -6,6 +6,9 @@ import * as T from "../../types";
import { concatPsString } from "../p-text-helpers";
function getBaseAndAdjectives(np: T.Rendered<T.NPSelection | T.EqCompSelection>): T.PsString[] {
if (np.type === "sandwich") {
return getSandwichPsBaseAndAdjectives(np);
}
const adjs = np.adjectives;
if (!adjs) {
return np.ps;
@ -22,6 +25,32 @@ function getBaseAndAdjectives(np: T.Rendered<T.NPSelection | T.EqCompSelection>)
));
}
function getSandwichPsBaseAndAdjectives(s: T.Rendered<T.SandwichSelection<T.Sandwich>>): T.PsString[] {
const insideBase = getBaseAndAdjectives(s.inside);
const willContractWithPronoun = s.before && s.before.p === "د" && s.inside.type === "pronoun"
&& (isFirstPerson(s.inside.person) || isSecondPerson(s.inside.person))
const contracted = (willContractWithPronoun && s.inside.type === "pronoun")
? contractPronoun(s.inside)
: undefined
return insideBase.map((inside) => (
concatPsString(
(s.before && !willContractWithPronoun) ? s.before : "",
s.before ? " " : "",
contracted ? contracted : inside,
s.after ? " " : "",
s.after ? s.after : "",
)
));
}
function contractPronoun(n: T.Rendered<T.PronounSelection>): T.PsString | undefined {
return isFirstPerson(n.person)
? concatPsString({ p: "ز", f: "z" }, n.ps[0])
: isSecondPerson(n.person)
? concatPsString({ p: "س", f: "s" }, n.ps[0])
: undefined;
}
function trimOffShrunkenPossesive(p: T.Rendered<T.NPSelection>): T.Rendered<T.NPSelection> {
if (!("possesor" in p)) {
return p;
@ -46,12 +75,20 @@ function trimOffShrunkenPossesive(p: T.Rendered<T.NPSelection>): T.Rendered<T.NP
export function getPashtoFromRendered(np: T.Rendered<T.NPSelection> | T.Rendered<T.EqCompSelection>, subjectsPerson: false | T.Person): T.PsString[] {
const base = getBaseAndAdjectives(np);
if (np.type !== "loc. adv." && np.type !== "adjective") {
// ts being dumb
const trimmed = trimOffShrunkenPossesive(np as T.Rendered<T.NPSelection>);
if (trimmed.possesor) {
return addPossesor(trimmed.possesor.np, base, subjectsPerson);
}
if (np.type === "loc. adv." || np.type === "adjective") {
return base;
}
const trimmed = np.type === "sandwich" ? {
...np,
inside: trimOffShrunkenPossesive(np.inside),
} : trimOffShrunkenPossesive(np);
if (trimmed.type === "sandwich") {
return trimmed.inside.possesor
? addPossesor(trimmed.inside.possesor.np, base, subjectsPerson)
: base;
}
if (trimmed.possesor) {
return addPossesor(trimmed.possesor.np, base, subjectsPerson);
}
return base;
}
@ -143,6 +180,9 @@ function pronounPossEng(p: T.Person): string {
}
export function getEnglishFromRendered(r: T.Rendered<T.NPSelection | T.EqCompSelection>): string | undefined {
if (r.type === "sandwich") {
return getEnglishFromRenderedSandwich(r);
}
if (!r.e) return undefined;
if (r.type === "loc. adv." || r.type === "adjective") {
return r.e;
@ -154,4 +194,10 @@ export function getEnglishFromRendered(r: T.Rendered<T.NPSelection | T.EqCompSel
}
// TODO: possesives in English for participles and pronouns too!
return r.e;
}
}
function getEnglishFromRenderedSandwich(r: T.Rendered<T.SandwichSelection<T.Sandwich>>): string | undefined {
const insideE = getEnglishFromRendered(r.inside);
if (!insideE) return undefined;
return `${r.e} ${insideE}`;
}

View File

@ -10,6 +10,7 @@ import { getEnglishWord } from "../get-english-word";
import { psStringFromEntry } from "../p-text-helpers";
import { isLocativeAdverbEntry } from "../type-predicates";
import { renderAdjectiveSelection } from "./render-adj";
import { renderSandwich } from "./render-sandwich";
export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
const king = (EP.subject.type === "pronoun")
@ -65,6 +66,9 @@ function renderEquative(es: T.EquativeSelection, person: T.Person): T.EquativeRe
}
function renderEqCompSelection(s: T.EqCompSelection, person: T.Person): T.Rendered<T.EqCompSelection> {
if (s.type === "sandwich") {
return renderSandwich(s);
}
const e = getEnglishWord(s.entry);
if (!e || typeof e !== "string") {
console.log(e);

View File

@ -14,7 +14,7 @@ import {
} from "../../lib/np-tools";
import { getEnglishWord } from "../get-english-word";
import { renderAdjectiveSelection } from "./render-adj";
import { isPattern5Entry, isUnisexAnimNounEntry } from "../type-predicates";
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";
@ -93,7 +93,7 @@ function renderPossesor(possesor: T.PossesorSelection | undefined, possesorRole:
if (!possesor) return undefined;
const isSingUnisexAnim5PatternNoun = (possesor.np.type === "noun"
&& possesor.np.number === "singular"
&& isUnisexAnimNounEntry(possesor.np.entry)
&& isAnimNounEntry(possesor.np.entry)
&& isPattern5Entry(possesor.np.entry)
);
return {

View File

@ -0,0 +1,26 @@
import * as T from "../../types";
import { isPattern1Entry, isPattern5Entry, isAnimNounEntry } from "../type-predicates";
import { renderNPSelection } from "./render-np";
export function renderSandwich(s: T.SandwichSelection<T.Sandwich>): T.Rendered<T.SandwichSelection<T.Sandwich>> {
const inflectInside = (isLocationSandwich(s) && s.inside.type === "noun" && isPattern1Entry(s.inside.entry) && s.inside.number === "singular")
? false
: (s.inside.type === "noun" && isPattern5Entry(s.inside.entry) && isAnimNounEntry(s.inside.entry))
? false
: true;
return {
...s,
inside: renderNPSelection(
s.inside,
true,
inflectInside,
"subject",
"none",
),
};
}
function isLocationSandwich(s: T.SandwichSelection<T.Sandwich>): boolean {
// TODO: more nuanced version of this?
return s.before?.p === "په" && s.after?.f === "کې";
}

View File

@ -28,20 +28,20 @@ import { renderNPSelection } from "./render-np";
export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
// Sentence Rules Logic
const isPast = isPastTense(VP.verb.tense);
const isTransitive = VP.verb.object !== "none";
const isTransitive = VP.object !== "none";
const { king, servant } = getKingAndServant(isPast, isTransitive);
const kingPerson = getPersonFromNP(
king === "subject" ? VP.subject : VP.verb.object,
king === "subject" ? VP.subject : VP.object,
);
// TODO: more elegant way of handling this type safety
if (kingPerson === undefined) {
throw new Error("king of sentance does not exist");
}
const subjectPerson = getPersonFromNP(VP.subject);
const objectPerson = getPersonFromNP(VP.verb.object);
const objectPerson = getPersonFromNP(VP.object);
// TODO: also don't inflect if it's a pattern one animate noun
const inflectSubject = isPast && isTransitive && !isMascSingAnimatePattern4(VP.subject);
const inflectObject = !isPast && isFirstOrSecondPersPronoun(VP.verb.object);
const inflectObject = !isPast && isFirstOrSecondPersPronoun(VP.object);
// Render Elements
const b: T.VPRendered = {
type: "VPRendered",
@ -51,11 +51,11 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
isTransitive,
isCompound: VP.verb.isCompound,
subject: renderNPSelection(VP.subject, inflectSubject, false, "subject", king === "subject" ? "king" : "servant"),
object: renderNPSelection(VP.verb.object, inflectObject, true, "object", king === "object" ? "king" : "servant"),
object: renderNPSelection(VP.object, inflectObject, true, "object", king === "object" ? "king" : "servant"),
verb: renderVerbSelection(VP.verb, kingPerson, objectPerson),
englishBase: renderEnglishVPBase({
subjectPerson,
object: VP.verb.isCompound === "dynamic" ? "none" : VP.verb.object,
object: VP.verb.isCompound === "dynamic" ? "none" : VP.object,
vs: VP.verb,
}),
form: VP.form,

View File

@ -197,48 +197,85 @@ export function switchSubjObj(vps: T.VPSelectionState): T.VPSelectionState;
export function switchSubjObj(vps: T.VPSelectionComplete): T.VPSelectionComplete;
export function switchSubjObj(vps: T.VPSelectionState | T.VPSelectionComplete): T.VPSelectionState | T.VPSelectionComplete {
if ("tenseCategory" in vps.verb) {
if (!vps.subject || !(typeof vps.verb.object === "object") || (vps.verb.tenseCategory === "imperative")) {
if (!vps.subject || !(typeof vps.object === "object") || (vps.verb.tenseCategory === "imperative")) {
return vps;
}
return {
...vps,
subject: vps.verb.object,
verb: {
...vps.verb,
object: vps.subject,
},
subject: vps.object,
object: vps.subject,
};
}
if (!vps.subject|| !vps.verb || !(typeof vps.verb.object === "object")) {
if (!vps.subject|| !vps.verb || !(typeof vps.object === "object")) {
return vps;
}
return {
...vps,
subject: vps.verb.object,
verb: {
...vps.verb,
object: vps.subject,
}
subject: vps.object,
object: vps.subject,
};
}
// export function insertSubjectSelection(vps: T.VPSelectionState, s: T.SubjectSelection): T.VPSelectionState {
// const index = vps.blocks.findIndex(f => f.type === "subjectSelection");
// if (index === -1) {
// throw new Error("couldn't find subjectSelection to insert over");
// }
// const blocks = [...vps.blocks];
// blocks[index] = s;
// return {
// ...vps,
// blocks,
// };
// }
// export function insertObjecttSelection(vps: T.VPSelectionState, o: T.ObjectSelection): T.VPSelectionState {
// const index = vps.blocks.findIndex(f => f.type === "objectSelection");
// if (index === -1) {
// throw new Error("couldn't find objectSelection to insert over");
// }
// const blocks = [...vps.blocks];
// blocks[index] = o;
// return {
// ...vps,
// blocks,
// };
// }
// export function getSubjectSelection(vps: T.VPSelectionState): T.SubjectSelection {
// const subject = vps.blocks.find(f => f.type === "subjectSelection");
// if (subject?.type !== "subjectSelection") {
// throw new Error("couldn't find subjectSelection");
// }
// return subject;
// }
// export function getObjectSelection(vps: T.VPSelectionState): T.ObjectSelection {
// const object = vps.blocks.find(f => f.type === "objectSelection");
// if (object?.type !== "objectSelection") {
// throw new Error("couldn't find objectSelection");
// }
// return object;
// }
export function completeVPSelection(vps: T.VPSelectionState): T.VPSelectionComplete | undefined {
if (vps.subject === undefined) {
return undefined;
}
if (vps.verb.object === undefined) {
if (vps.object === undefined) {
return undefined;
}
// necessary for this version on typscript ...
const verb: T.VerbSelectionComplete = {
...vps.verb,
object: vps.verb.object,
tense: getTenseFromVerbSelection(vps.verb),
};
const subject = vps.subject;
const object = vps.object
return {
...vps,
subject,
object,
verb,
};
}
@ -264,9 +301,9 @@ export function isThirdPerson(p: T.Person): boolean {
export function ensure2ndPersSubjPronounAndNoConflict(vps: T.VPSelectionState): T.VPSelectionState {
console.log("checking more...", vps);
const subjIs2ndPerson = (vps.subject?.type === "pronoun") && isSecondPerson(vps.subject.person);
const objIs2ndPerson = (typeof vps.verb.object === "object")
&& (vps.verb.object.type === "pronoun")
&& isSecondPerson(vps.verb.object.person);
const objIs2ndPerson = (typeof vps.object === "object")
&& (vps.object.type === "pronoun")
&& isSecondPerson(vps.object.person);
console.log({ subjIs2ndPerson, objIs2ndPerson });
const default2ndPersSubject: T.PronounSelection = {
type: "pronoun",
@ -284,22 +321,19 @@ export function ensure2ndPersSubjPronounAndNoConflict(vps: T.VPSelectionState):
return vps;
}
if (subjIs2ndPerson && objIs2ndPerson) {
if (typeof vps.verb.object !== "object" || vps.verb.object.type !== "pronoun") {
if (typeof vps.object !== "object" || vps.object.type !== "pronoun") {
return vps;
}
return {
...vps,
verb: {
...vps.verb,
object: {
...vps.verb.object,
person: getNon2ndPersPronoun(),
},
object: {
...vps.object,
person: getNon2ndPersPronoun(),
},
};
}
if (!subjIs2ndPerson && objIs2ndPerson) {
if (typeof vps.verb.object !== "object" || vps.verb.object.type !== "pronoun") {
if (typeof vps.object !== "object" || vps.object.type !== "pronoun") {
return {
...vps,
subject: default2ndPersSubject,
@ -307,13 +341,10 @@ export function ensure2ndPersSubjPronounAndNoConflict(vps: T.VPSelectionState):
}
return {
...vps,
subject: default2ndPersSubject,
verb: {
...vps.verb,
object: {
...vps.verb.object,
person: getNon2ndPersPronoun(),
},
subject: default2ndPersSubject,
object: {
...vps.object,
person: getNon2ndPersPronoun(),
},
};
}

97
src/lib/sandwiches.ts Normal file
View File

@ -0,0 +1,97 @@
import * as T from "../types";
export const sandwiches: T.Sandwich[] = [
{
type: "sandwich",
before: { p: "له", f: "la" },
after: { p: "نه", f: "na" },
e: "from",
},
{
type: "sandwich",
before: { p: "له", f: "la" },
after: { p: "څخه", f: "tsuxa" },
e: "from",
},
// TODO: Implement mayonaise
// {
// type: "sandwich",
// before: { p: "له", f: "la" },
// after: "mayonaise",
// e: "from",
// },
{
type: "sandwich",
before: { p: "له", f: "la" },
after: { p: "سره", f: "sara" },
e: "with",
},
{
type: "sandwich",
before: undefined,
after: { p: "ته", f: "ta" },
e: "to",
},
{
type: "sandwich",
before: { p: "د", f: "du" },
after: { p: "په څانګ", f: "pu tsaang" },
e: "beside",
},
{
type: "sandwich",
before: { p: "پر", f: "pur" },
after: { p: "باندې", f: "baande" },
e: "on",
},
{
type: "sandwich",
before: { p: "په", f: "pu" },
after: { p: "کې", f: "ke" },
e: "in",
},
{
type: "sandwich",
before: { p: "د", f: "du" },
after: { p: "دننه", f: "dununa" },
e: "inside",
},
{
type: "sandwich",
before: { p: "د", f: "du" },
after: { p: "دباندې", f: "dubaande" },
e: "outside",
},
{
type: "sandwich",
before: { p: "د", f: "du" },
after: { p: "مخې ته", f: "mukhe ta" },
e: "in front of",
},
{
type: "sandwich",
before: { p: "د", f: "du" },
after: { p: "شا ته", f: "shaa ta" },
e: "behind",
},
{
type: "sandwich",
before: { p: "د", f: "du" },
after: { p: "لاندې", f: "laande" },
e: "under",
},
{
type: "sandwich",
before: { p: "د", f: "du" },
after: { p: "په شان", f: "pu shaan" },
e: "like",
},
{
type: "sandwich",
before: { p: "د", f: "du" },
after: { p: "غوندې", f: "ghwunde" },
e: "like",
},
];
export default sandwiches;

View File

@ -49,8 +49,12 @@ export function isUnisexNounEntry(e: T.NounEntry | T.AdjectiveEntry): e is T.Uni
return isNounEntry(e) && e.c.includes("unisex");
}
export function isAnimNounEntry(e: T.NounEntry | T.AdverbEntry): e is T.AnimNounEntry {
return e.c.includes("anim.");
}
export function isUnisexAnimNounEntry(e: T.NounEntry | T.AdjectiveEntry): e is T.UnisexAnimNounEntry {
return isUnisexNounEntry(e) && e.c.includes("anim.");
return isUnisexNounEntry(e) && isAnimNounEntry(e);
}
export function isAdjOrUnisexNounEntry(e: T.Entry): e is (T.AdjectiveEntry | T.UnisexNounEntry) {
@ -191,4 +195,3 @@ export function isEquativeTense(t: T.Tense): t is T.EquativeTense {
export function isImperativeTense(tense: T.Tense): tense is T.ImperativeTense {
return tense === "imperfectiveImperative" || tense === "perfectiveImperative";
}

View File

@ -472,6 +472,7 @@ export type AayTail = "ey" | "aay";
export type NounEntry = DictionaryEntry & { c: string } & { __brand: "a noun entry" };
export type MascNounEntry = NounEntry & { __brand2: "a masc noun entry" };
export type FemNounEntry = NounEntry & { __brand2: "a fem noun entry" };
export type AnimNounEntry = NounEntry & { __brand3: "a anim noun entry" };
export type UnisexNounEntry = MascNounEntry & { __brand3: "a unisex noun entry" };
export type UnisexAnimNounEntry = UnisexNounEntry & { __brand4: "an anim unisex noun entry" };
export type AdverbEntry = DictionaryEntry & { c: string } & { __brand: "an adverb entry" };
@ -529,20 +530,43 @@ export type ModalTense = `${VerbTense}Modal`;
export type ImperativeTense = `${Aspect}Imperative`;
export type Tense = EquativeTense | VerbTense | PerfectTense | ModalTense | ImperativeTense;
export type SubjectSelection = {
type: "subjectSelection",
selection: NPSelection | undefined,
};
export type ObjectSelection = {
type: "objectSelection",
selection: NPSelection | ObjectNP | undefined,
};
export type SubjectSelectionComplete = {
type: "subjectSelectionComplete",
selection: NPSelection,
};
export type ObjectSelectionComplete = {
type: "objectSelection",
selection: NPSelection | ObjectNP,
};
export type VPSelectionState = {
// blocks: (SubjectSelection | ObjectSelection)[]
subject: NPSelection | undefined,
object: NPSelection | ObjectNP | undefined,
verb: VerbSelection,
form: FormVersion,
};
export type VPSelectionComplete = {
// blocks: (SubjectSelectionComplete | ObjectSelectionComplete)[]
subject: NPSelection,
object: NPSelection | ObjectNP,
verb: VerbSelectionComplete,
form: FormVersion,
};
export type VerbSelectionComplete = Omit<VerbSelection, "object" | "verbTense" | "perfectTense" | "imperfectiveTense" | "tenseCategory"> & {
object: Exclude<VerbObject, undefined>,
tense: VerbTense | PerfectTense | ModalTense | ImperativeTense,
}
@ -550,7 +574,6 @@ export type VerbSelection = {
type: "verb",
verb: VerbEntry,
dynAuxVerb?: VerbEntry,
object: VerbObject, // TODO: should have a locked in (but number changeable noun) here for dynamic compounds
transitivity: Transitivity,
canChangeTransitivity: boolean,
canChangeStatDyn: boolean,
@ -586,6 +609,8 @@ export type VerbObject =
export type NPSelection = NounSelection | PronounSelection | ParticipleSelection;
export type APSelection = AdverbSelection | SandwichSelection<Sandwich>;
export type NPType = "noun" | "pronoun" | "participle";
export type ObjectNP = "none" | Person.ThirdPlurMale;
@ -608,6 +633,11 @@ export type NounSelection = {
possesor: undefined | PossesorSelection,
};
export type AdverbSelection = {
type: "adverb",
entry: AdverbEntry,
}
export type AdjectiveSelection = {
type: "adjective",
entry: AdjectiveEntry,
@ -645,23 +675,27 @@ export type RenderedPossesorSelection = {
shrunken: boolean,
};
export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelection> = ReplaceKey<
Omit<T, "changeGender" | "changeNumber" | "changeDistance" | "adjectives" | "possesor">,
"e",
string
> & {
ps: PsString[],
e?: string,
inflected: boolean,
person: Person,
role: "king" | "servant" | "none",
// TODO: better recursive thing
adjectives?: Rendered<AdjectiveSelection>[],
possesor?: {
shrunken: boolean,
np: Rendered<NPSelection>,
},
};
export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelection | SandwichSelection<Sandwich>> = T extends SandwichSelection<Sandwich>
? Omit<SandwichSelection<Sandwich>, "inside"> & {
inside: Rendered<NPSelection>,
}
: ReplaceKey<
Omit<T, "changeGender" | "changeNumber" | "changeDistance" | "adjectives" | "possesor">,
"e",
string
> & {
ps: PsString[],
e?: string,
inflected: boolean,
person: Person,
role: "king" | "servant" | "none",
// TODO: better recursive thing
adjectives?: Rendered<AdjectiveSelection>[],
possesor?: {
shrunken: boolean,
np: Rendered<NPSelection>,
},
};
// TODO: recursive changing this down into the possesor etc.
export type EPSelectionState = {
@ -687,8 +721,19 @@ export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"
omitSubject: boolean,
};
export type EqCompType = "adjective" | "loc. adv."; // TODO: - more
export type EqCompSelection = AdjectiveSelection | LocativeAdverbSelection; // TODO: - more
export type EqCompType = "adjective" | "loc. adv." | "sandwich"
export type EqCompSelection = AdjectiveSelection | LocativeAdverbSelection | SandwichSelection<Sandwich>;
export type SandwichSelection<S extends Sandwich> = S & {
inside: NPSelection,
};
export type Sandwich = {
type: "sandwich",
before: PsString | undefined,
after: PsString | undefined,
e: string,
};
export type EquativeSelection = {
tense: EquativeTense,