big improvements and equative explorer/machine working!

This commit is contained in:
lingdocs 2022-04-26 12:11:46 +05:00
parent 9b0c451458
commit b6ebdd66ca
39 changed files with 1310 additions and 351 deletions

53
nouns-adjs/loc-adverbs.js Normal file
View File

@ -0,0 +1,53 @@
module.exports = [
{ ts: 1527822780, e: `forward, towards` }, // ورمخته - wărmukhta
{ ts: 1527820292, e: `behind` }, // شاته - shaata
{ ts: 1527814912, e: `near, close to` }, // رانږدې - raanaGde
{ ts: 1527813928, e: `right here, in this very place` }, // همدلته - hamdalta
{ ts: 1610454983695, e: `at this place, at that place, here` }, // دغلته - daghalta
{ ts: 1527813279, e: `in front of, forward, ahead of` }, // مخته - mukhta
{ ts: 1527822042, e: `above, overhead` }, // راپورته - raaporta
{ ts: 1588758935200, e: `that way, direction, over there` }, // وراخوا - wăraakhwaa
{ ts: 1527817302, e: `together, in one place` }, // یوځای - yodzaay
{ ts: 1527812558, e: `here` }, // دلته - dălta
{ ts: 1527814911, e: `near, close to, almost` }, // نږدې - naGde
{ ts: 1578080952673, e: `outside, outside of, beyond` }, // دباندې - dubaande
{ ts: 1527812780, e: `down, beneath` }, // ښکته - xkuta
{ ts: 1527813688, e: `lower, below, down` }, // کوز - kooz
{ ts: 1610796256372, e: `forward` }, // مخ په وړاندې - mukh pu wRaande
{ ts: 1527812293, e: `with you` }, // درسره - dărsara
{ ts: 1527812935, e: `face to face, across from, opposite; straight` }, // مخامخ - mukhaamukh
{ ts: 1527815420, e: `later, after, behind` }, // وروسته - wroosta
{ ts: 1527811221, e: `above, overhead` }, // پورته - porta
{ ts: 1527813690, e: `above, on top` }, // بر - bar
{ ts: 1527822803, e: `far, distantly, a bit further, to the side, back, away, direction` }, // هیسته - héesta
{ ts: 1579824262965, e: `together, in one place; at the same time` }, // یو ځای - yo dzaay
{ ts: 1527814913, e: `near, close to` }, // ورنږدې - warnaGde
{ ts: 1527814318, e: `in front, forward, before` }, // مخکښې - mukhkxe
{ ts: 1527812413, e: `after oneself` }, // راپسې - raapase
{ ts: 1527822852, e: `this way and that way, here and there` }, // دېخوا هاخوا - dekhwaa haakhwaa
{ ts: 1527812449, e: `there` }, // هلته - hálta, álta
{ ts: 1610796249240, e: `forward` }, // مخ پر وړاندې - mukh pur wRaande
{ ts: 1527819393, e: `over/across to you/there` }, // درپورې - darpore
{ ts: 1527821253, e: `near, close (نژدې)` }, // نزدې - nizde, nazde
{ ts: 1527813122, e: `inside, within, interior` }, // دننه - dununa
{ ts: 1527817710, e: `that side, across` }, // ها خوا - haakhwaa
{ ts: 1581189430959, e: `ahead, in front; earlier, first, before` }, // پېش - pesh
{ ts: 1527823145, e: `outside, from without, on the outside; abroad (as in out of country)` }, // بېرون - beróon
{ ts: 1527814605, e: `far, distant` }, // لرې - lure
{ ts: 1527819438, e: `far, distant, removed` }, // لېرې - lere
{ ts: 1578015603537, e: `above` }, // له پاسه - la paasa
{ ts: 1588780597931, e: `near, close to, almost` }, // نېږدې - neGdé
{ ts: 1527814708, e: `close, near, soon, almost` }, // نژدې - nijzde, najzde
{ ts: 1594909066356, e: `around, in the area` }, // خوا و شا - khwaa-U-shaa
{ ts: 1577999518050, e: `there, over there, thither` }, // هورې - hooré
{ ts: 1527815416, e: `ahead, forward, before, earlier` }, // وړاندې - wRaande
{ ts: 1527818064, e: `after him/her/it/them, behind` }, // ورپسې - warpase
{ ts: 1527818186, e: `after you (sing and plural)` }, // درپسې - dărpase
{ ts: 1527818069, e: `in front of him/her/it/them` }, // وروړاندې - wărwRaande
{ ts: 1527819321, e: `down, below; slope` }, // کښته - kxúta
{ ts: 1527820805, e: `right there, in the same place, in the very same place` }, // هماغلته - hamáaghalta
{ ts: 1527815457, e: `all over, throughout` }, // سراسري - săraasaree
{ ts: 1527819388, e: `inside, in` }, // وردننه - wardunúna
{ ts: 1527812375, e: `back (as in return)` }, // واپس - waapus
{ ts: 1527817294, e: `up, above` }, // پاس - paas
];

View File

@ -5,7 +5,6 @@ module.exports = [
{ ts: 1588857967561, e: `bull` }, // غوایه - ghwaayú { ts: 1588857967561, e: `bull` }, // غوایه - ghwaayú
{ ts: 1527817108, e: `look, gaze, examination, inspection, spying` }, // کاته - kaatu { ts: 1527817108, e: `look, gaze, examination, inspection, spying` }, // کاته - kaatu
{ ts: 1527817768, e: `raven, crow` }, // کارګه - kaargu { ts: 1527817768, e: `raven, crow` }, // کارګه - kaargu
{ ts: 1527819245, e: `master of house, head of family, married man` }, // کوربانه - korbaanú
{ ts: 1527818516, e: `swimming, bathing` }, // لمبېده - lambedú { ts: 1527818516, e: `swimming, bathing` }, // لمبېده - lambedú
{ ts: 1527813986, e: `sunset, west` }, // لمر پرېواته - lmarprewaatu { ts: 1527813986, e: `sunset, west` }, // لمر پرېواته - lmarprewaatu
{ ts: 1527813992, e: `sunset` }, // لمر لوېده - lmarlwedu { ts: 1527813992, e: `sunset` }, // لمر لوېده - lmarlwedu

View File

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

View File

@ -23,7 +23,7 @@ import {
Modal Modal
} from "react-bootstrap"; } from "react-bootstrap";
import * as T from "./types"; import * as T from "./types";
import { isNounEntry } from "./lib/type-predicates"; import { isAdjectiveEntry, isLocativeAdverbEntry, isNounEntry } from "./lib/type-predicates";
import defualtTextOptions from "./lib/default-text-options"; import defualtTextOptions from "./lib/default-text-options";
import PhraseBuilder from "./components/vp-explorer/VPExplorer"; import PhraseBuilder from "./components/vp-explorer/VPExplorer";
import useStickyState from "./lib/useStickyState"; import useStickyState from "./lib/useStickyState";
@ -35,6 +35,14 @@ const verbTypes: VerbType[] = [
]; ];
const nouns = nounsAdjs.filter(isNounEntry); const nouns = nounsAdjs.filter(isNounEntry);
const adjectives = nounsAdjs.filter(isAdjectiveEntry);
const locativeAdverbs = nounsAdjs.filter(isLocativeAdverbEntry);
const entryFeeder: T.EntryFeeder = {
locativeAdverbs,
nouns,
adjectives,
verbs,
};
const transitivities: T.Transitivity[] = [ const transitivities: T.Transitivity[] = [
"transitive", "transitive",
@ -47,6 +55,8 @@ const allVerbs = verbs.map((v: { entry: T.DictionaryEntry, complement?: T.Dictio
info: getVerbInfo(v.entry, v.complement), info: getVerbInfo(v.entry, v.complement),
})); }));
function App() { function App() {
const [verbTs, setVerbTs] = useStickyState<number>(0, "verbTs1"); const [verbTs, setVerbTs] = useStickyState<number>(0, "verbTs1");
const [verbTypeShowing, setVerbTypeShowing] = useStickyState<VerbType>("simple", "vTypeShowing"); const [verbTypeShowing, setVerbTypeShowing] = useStickyState<VerbType>("simple", "vTypeShowing");
@ -215,6 +225,7 @@ function App() {
name="verb-type" name="verb-type"
checked={verbTypeShowing === type} checked={verbTypeShowing === type}
value={type} value={type}
onChange={() => null}
/> />
<label className="form-check-label"> <label className="form-check-label">
{type} {type}
@ -244,6 +255,7 @@ function App() {
type="radio" type="radio"
name="transitivity" name="transitivity"
checked={transitivityShowing === transitivity} checked={transitivityShowing === transitivity}
onChange={() => null}
value={transitivity} value={transitivity}
/> />
<label className="form-check-label"> <label className="form-check-label">
@ -260,8 +272,7 @@ function App() {
<PhraseBuilder <PhraseBuilder
handleLinkClick="none" handleLinkClick="none"
verb={v.verb as T.VerbEntry} verb={v.verb as T.VerbEntry}
nouns={nouns} entryFeeder={entryFeeder}
verbs={verbs}
opts={textOptions} opts={textOptions}
/> />
</div>} </div>}

View File

@ -27,12 +27,8 @@ export const customStyles: StylesConfig = {
}), }),
} }
function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: ({ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
entries: E[] entryFeeder: T.EntryFeederSingleType<E>,
} | {
searchF: (search: string) => E[],
getByTs: (ts: number) => E | undefined,
}) & {
value: E | undefined, value: E | undefined,
onChange: (value: E | undefined) => void, onChange: (value: E | undefined) => void,
name: string | undefined, name: string | undefined,
@ -49,17 +45,19 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: ({
return makeSelectOption(e, props.opts); return makeSelectOption(e, props.opts);
} }
const value = props.value ? makeOption(props.value) : undefined; const value = props.value ? makeOption(props.value) : undefined;
if ("searchF" in props) { if ("search" in props.entryFeeder) {
const search = props.entryFeeder.search;
const getByTs = props.entryFeeder.getByTs;
const options = (searchString: string) => const options = (searchString: string) =>
new Promise<{ value: string, label: string | JSX.Element }[]>(resolve => { new Promise<{ value: string, label: string | JSX.Element }[]>(resolve => {
resolve(props.searchF(searchString).map(makeOption)); resolve(search(searchString).map(makeOption));
}); });
const onChange = (v: { label: string | JSX.Element, value: string } | null) => { const onChange = (v: { label: string | JSX.Element, value: string } | null) => {
if (!v) { if (!v) {
props.onChange(undefined); props.onChange(undefined);
return; return;
} }
const s = props.getByTs(parseInt(v.value)); const s = getByTs(parseInt(v.value));
if (!s) return; if (!s) return;
props.onChange(s); props.onChange(s);
} }
@ -77,7 +75,8 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: ({
/> />
</div>; </div>;
} }
const options = props.entries const entries = props.entryFeeder;
const options = entries
.sort((a, b) => { .sort((a, b) => {
if ("entry" in a) { if ("entry" in a) {
return a.entry.p.localeCompare("p" in b ? b.p : b.entry.p, "af-PS") return a.entry.p.localeCompare("p" in b ? b.p : b.entry.p, "af-PS")
@ -90,7 +89,7 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: ({
props.onChange(undefined); props.onChange(undefined);
return; return;
} }
const s = props.entries.find(e => ( const s = entries.find(e => (
("entry" in e) ("entry" in e)
? e.entry.ts.toString() === v.value ? e.entry.ts.toString() === v.value
: e.ts.toString() === v.value : e.ts.toString() === v.value

View File

@ -0,0 +1,85 @@
import * as T from "../../types";
import { renderEP } from "../../lib/phrase-building/render-ep";
import { compileEP } from "../../lib/phrase-building/compile-ep";
import AbbreviationFormSelector from "../vp-explorer/AbbreviationFormSelector";
import useStickyState from "../../lib/useStickyState";
import Examples from "../Examples";
function EPDisplay({ eps, opts }: { eps: T.EPSelectionState, opts: T.TextOptions }) {
const [form, setForm] = useStickyState<T.FormVersion>({ removeKing: false, shrinkServant: false }, "abbreviationFormEq");
const EP = completeEPSelection(eps);
if (!EP) {
return <div className="lead text-center my-4">
{(!eps.subject && !eps.predicate[eps.predicate.type])
? "Select Subject and Predicate"
: (eps.subject && !eps.predicate[eps.predicate.type])
? "Select Predicate"
: (!eps.subject && eps.predicate[eps.predicate.type])
? "Select Subject"
: ""}
</div>
}
const rendered = renderEP(EP);
const result = compileEP(rendered, form);
return <div className="text-center pt-3">
<AbbreviationFormSelector
adjustable="king"
form={{ ...form, shrinkServant: false }}
onChange={setForm}
/>
{"long" in result.ps ?
<div>
<VariationLayer vs={result.ps.long} opts={opts} />
<VariationLayer vs={result.ps.short} opts={opts} />
{result.ps.mini && <VariationLayer vs={result.ps.mini} opts={opts} />}
</div>
: <VariationLayer vs={result.ps} opts={opts} />
}
{result.e && <div className="text-muted mt-3">
{result.e.map((e, i) => <div key={i}>{e}</div>)}
</div>}
{EP.predicate.selection.type === "participle" && <div style={{ maxWidth: "6 00px", margin: "0 auto" }} className="alert alert-warning mt-3 pt-4">
<p> NOTE: This means that the subject {rendered.subject.e ? `(${rendered.subject.e})` : ""} is <strong>the action/idea</strong> of
{` `}
"{rendered.predicate.e ? rendered.predicate.e : "the particple"}".</p>
<p>It <strong>does not</strong> mean that the subject is doing the action, which is what the transaltion sounds like in English.</p>
</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>;
}
function completeEPSelection(eps: T.EPSelectionState): T.EPSelectionComplete | undefined {
if (!eps.subject) {
return undefined;
}
if (eps.predicate.type === "Complement") {
const selection = eps.predicate.Complement;
if (!selection) return undefined;
return {
...eps,
subject: eps.subject,
predicate: {
type: "Complement",
selection,
},
};
}
// predicate is NP
const selection = eps.predicate.NP;
if (!selection) return undefined;
return {
...eps,
subject: eps.subject,
predicate: {
type: "NP",
selection,
},
};
}
export default EPDisplay;

View File

@ -0,0 +1,213 @@
import * as T from "../../types";
import useStickyState from "../../lib/useStickyState";
import NPPicker from "../np-picker/NPPicker";
import EquativePicker from "./EquativePicker";
import EPDisplay from "./EPDisplay";
import ButtonSelect from "../ButtonSelect";
import EqCompPicker from "./eq-comp-picker/EqCompPicker";
import { roleIcon } from "../vp-explorer/VPExplorerExplanationModal";
import { isUnisexNounEntry } from "../../lib/type-predicates";
import {
personGender,
personNumber,
} from "../../lib/misc-helpers";
import EqChartsDisplay from "./EqChartsDisplay";
// TODO: put the clear button beside the title in the predicate picker?
function EPExplorer(props: {
opts: T.TextOptions,
entryFeeder: T.EntryFeeder,
}) {
const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode");
const [eps, setEps] = useStickyState<T.EPSelectionState>({
subject: undefined,
predicate: {
type: "Complement",
NP: undefined,
Complement: undefined,
},
equative: {
tense: "present",
negative: false,
},
}, "EPSelectionState");
function handlePredicateTypeChange(type: "NP" | "Complement") {
setEps(o => ({
...o,
predicate: {
...o.predicate,
type,
},
}));
}
function handleSetSubject(subject: T.NPSelection | undefined) {
setEps(old => massageSubjectChange(subject, old));
}
function setPredicateNP(selection: T.NPSelection | undefined) {
setEps(o => massageNPPredicateChange(selection, o))
}
function setPredicateComp(selection: T.EqCompSelection | undefined) {
setEps(o => ({
...o,
predicate: {
...o.predicate,
Complement: selection
},
}));
}
const king = eps.subject?.type === "pronoun"
? "subject"
: eps.predicate.type === "Complement"
? "subject"
: "predicate";
return <div>
<div className="mt-2 mb-3 text-center">
<ButtonSelect
value={mode}
options={[
{ label: "Charts", value: "charts" },
{ label: "Phrases", value: "phrases" },
]}
handleChange={setMode}
/>
</div>
<div className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
{mode === "phrases" && <>
<div className="my-2">
<NPPicker
heading={<div className="h5 text-center">Subject {king === "subject" ? roleIcon.king : ""}</div>}
entryFeeder={props.entryFeeder}
np={eps.subject}
counterPart={undefined}
role="subject"
onChange={handleSetSubject}
opts={props.opts}
/>
</div>
<div className="my-2">
<div className="h5 text-center">Predicate {king === "predicate" ? roleIcon.king : ""}</div>
<div className="mb-2 text-center">
<ButtonSelect
small
options={[
{ value: "NP", label: "NP" },
{ value: "Complement", label: "Complement" },
]}
value={eps.predicate.type}
handleChange={handlePredicateTypeChange}
/>
</div>
{eps.predicate.type === "NP" ? <NPPicker
entryFeeder={props.entryFeeder}
np={eps.predicate.type === "NP" ? eps.predicate.NP : undefined}
counterPart={undefined}
role="subject"
onChange={setPredicateNP}
opts={props.opts}
/> : <EqCompPicker
comp={eps.predicate.type === "Complement" ? eps.predicate.Complement : undefined}
onChange={setPredicateComp}
opts={props.opts}
entryFeeder={props.entryFeeder}
/>}
</div>
</>}
<div className="my-2">
<div className="h5 text-center clickable">Equative</div>
<EquativePicker
equative={eps.equative}
onChange={(equative) => setEps(o => ({ ...o, equative }))}
hideNegative={mode === "charts"}
/>
</div>
</div>
{mode === "charts" && <EqChartsDisplay tense={eps.equative.tense} opts={props.opts} />}
{mode === "phrases" && <EPDisplay opts={props.opts} eps={eps} />}
</div>;
}
export default EPExplorer;
function massageNPPredicateChange(selection: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
if (!selection) {
return {
...old,
predicate: {
...old.predicate,
NP: selection,
},
};
}
if (old.subject?.type === "pronoun" && selection.type === "noun" && isUnisexNounEntry(selection.entry)) {
const { gender, number } = selection;
const pronoun = old.subject.person;
const newPronoun = movePersonNumber(movePersonGender(pronoun, gender), number);
return {
...old,
subject: {
...old.subject,
person: newPronoun,
},
predicate: {
...old.predicate,
NP: selection,
},
};
}
return {
...old,
predicate: {
...old.predicate,
NP: selection,
},
};
}
function massageSubjectChange(subject: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
if (!subject) {
return {
...old,
subject,
};
}
if (subject.type === "pronoun" && old.predicate.type === "NP" && old.predicate.NP?.type === "noun" && isUnisexNounEntry(old.predicate.NP.entry)) {
const predicate = old.predicate.NP;
const numberAdjusted = predicate.changeNumber
? predicate.changeNumber(personNumber(subject.person))
: predicate;
const fullyAdjusted = numberAdjusted.changeGender
? numberAdjusted.changeGender(personGender(subject.person))
: numberAdjusted;
return {
...old,
subject,
predicate: {
...old.predicate,
NP: fullyAdjusted,
},
};
}
return {
...old,
subject,
};
}
function movePersonGender(p: T.Person, gender: T.Gender): T.Person {
const pGender = personGender(p);
if (gender === pGender) {
return p;
}
return (gender === "masc") ? (p - 1) : (p + 1);
}
function movePersonNumber(p: T.Person, number: T.NounNumber): T.Person {
const pNumber = personNumber(p);
if (pNumber === number) {
return p;
}
return (number === "plural")
? (p + 6)
: (p - 6);
}

View File

@ -0,0 +1,24 @@
import * as T from "../../types";
import VerbFormDisplay from "../VerbFormDisplay";
import { baParticle } from "../../lib/grammar-units";
import { getEquativeForm } from "../../lib/phrase-building/render-ep";
import { addToForm } from "../../lib/p-text-helpers";
function EqChartsDisplay({ tense, opts }: {
tense: T.EquativeTense,
opts: T.TextOptions,
}) {
const { form, hasBa } = getEquativeForm(tense);
const withBa = hasBa
? addToForm([baParticle, " ", { p: "…", f: "…" }, " "], form)
: form;
return <div className="mb-4">
<VerbFormDisplay
displayForm={withBa}
showingFormInfo={false}
textOptions={opts}
/>
</div>
};
export default EqChartsDisplay;

View File

@ -0,0 +1,107 @@
import * as T from "../../types"
import Select from "react-select";
import ButtonSelect from "../ButtonSelect";
export const zIndexProps = {
menuPortalTarget: document.body,
styles: { menuPortal: (base: any) => ({ ...base, zIndex: 9999 }) },
};
const options: { label: string | JSX.Element, value: T.EquativeTense }[] = [{
label: "Present",
value: "present",
}, {
label: "Habitual",
value: "habitual",
}, {
label: "Subjunctive",
value: "subjunctive",
}, {
label: "Future",
value: "future",
}, {
label: "Past",
value: "past",
}, {
label: `"Would Be"`,
value: "wouldBe",
}, {
label: "Past Subjunctive",
value: "pastSubjunctive",
}];
function EquativePicker({ equative, onChange, hideNegative }: {
equative: { tense: T.EquativeTense, negative: boolean },
onChange: (e: { tense: T.EquativeTense, negative: boolean }) => void,
hideNegative?: boolean,
}) {
function onTenseSelect(o: { value: T.EquativeTense } | null) {
const value = o?.value ? o.value : undefined;
if (!value) {
return;
}
onChange({
...equative,
tense: value,
});
}
function moveTense(dir: "forward" | "back") {
return () => {
const currIndex = options.findIndex(tn => tn.value === equative.tense)
if (currIndex === -1) {
console.error("error moving tense", dir);
return;
}
const newIndex = dir === "forward"
? ((currIndex + 1) % options.length)
: (currIndex === 0 ? (options.length - 1) : (currIndex - 1))
const newTense = options[newIndex];
onChange({
...equative,
tense: newTense.value,
});
};
}
function onPosNegSelect(value: "true" | "false") {
onChange({
...equative,
negative: value === "true",
});
}
return <div>
<div style={{ maxWidth: "300px", minWidth: "250px", margin: "0 auto" }}>
<div className="h5">Tense:</div>
<Select
isSearchable={false}
// for some reason can't use tOptions with find here;
value={options.find(o => o.value === equative.tense)}
onChange={onTenseSelect}
className="mb-2"
options={options}
{...zIndexProps}
/>
{<div className="d-flex flex-row justify-content-between align-items-center mt-3 mb-1" style={{ width: "100%" }}>
<div className="btn btn-light clickable" onClick={moveTense("back")}>
<i className="fas fa-chevron-left" />
</div>
{!hideNegative && <ButtonSelect
small
value={equative.negative.toString() as "true" | "false"}
options={[{
label: "Pos.",
value: "false",
}, {
label: "Neg.",
value: "true",
}]}
handleChange={onPosNegSelect}
/>}
<div onClick={moveTense("forward")} className="btn btn-light clickable">
<i className="fas fa-chevron-right" />
</div>
</div>}
</div>
</div>;
}
export default EquativePicker;

View File

@ -0,0 +1,77 @@
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."];
function EqCompPicker(props: {
onChange: (comp: T.EqCompSelection | undefined) => void,
comp: T.EqCompSelection | undefined,
opts: T.TextOptions,
cantClear?: boolean,
heading?: JSX.Element | string,
entryFeeder: T.EntryFeeder,
}) {
const [compType, setCompType] = useState<T.EqCompType | undefined>(props.comp ? props.comp.type : undefined);
useEffect(() => {
setCompType(props.comp ? props.comp.type : undefined);
}, [props.comp]);
function handleClear() {
setCompType(undefined);
props.onChange(undefined);
}
function handleCompTypeChange(ctp: T.EqCompType) {
props.onChange(undefined);
setCompType(ctp);
}
const clearButton = (compType && !props.cantClear)
? <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>
: <div></div>;
return <>
<div className="d-flex flex-row justify-content-between">
<div></div>
<div>
{typeof props.heading === "string"
? <div className="h5 text-center">{props.heading}</div>
: props.heading}
</div>
<div>
{clearButton}
</div>
</div>
{!compType && <div className="text-center">
<div className="h6 mr-3">
Choose Complement
</div>
{compTypes.map((cpt) => <div key={cpt} className="mb-2">
<button
key={cpt}
type="button"
className="mr-2 btn btn-sm btn-outline-secondary"
onClick={() => handleCompTypeChange(cpt)}
>
{cpt}
</button>
</div>)}
</div>}
<div style={{ minWidth: "9rem" }}>
{compType === "adjective" ?
<AdjectivePicker
entryFeeder={props.entryFeeder.adjectives}
adjective={props.comp?.type === "adjective" ? props.comp : undefined}
opts={props.opts}
onChange={props.onChange}
/>
: compType === "loc. adv."
? <LocativeAdverbPicker
entryFeeder={props.entryFeeder.locativeAdverbs}
adjective={props.comp?.type === "loc. adv." ? props.comp : undefined}
opts={props.opts}
onChange={props.onChange}
/>
: null}
</div>
</>;
}
export default EqCompPicker;

View File

@ -0,0 +1,37 @@
import * as T from "../../../types";
import EntrySelect from "../../EntrySelect";
function LocativeAdverbPicker(props: {
entryFeeder: T.EntryFeederSingleType<T.LocativeAdverbEntry>,
adjective: T.LocativeAdverbSelection | undefined,
onChange: (p: T.LocativeAdverbSelection | undefined) => void,
opts: T.TextOptions,
}) {
function onEntrySelect(entry: T.LocativeAdverbEntry | undefined) {
if (!entry) {
return props.onChange(undefined);
}
props.onChange(makeLocativeAdverbSelection(entry));
}
return <div style={{ maxWidth: "225px", minWidth: "125px" }}>
<h6>Loc. Adverb</h6>
<div>
<EntrySelect
value={props.adjective?.entry}
entryFeeder={props.entryFeeder}
onChange={onEntrySelect}
name="Locative Adverb"
opts={props.opts}
/>
</div>
</div>;
}
export default LocativeAdverbPicker;
function makeLocativeAdverbSelection(entry: T.LocativeAdverbEntry): T.LocativeAdverbSelection {
return {
type: "loc. adv.",
entry: entry,
};
}

View File

@ -0,0 +1,37 @@
import * as T from "../../types";
import EntrySelect from "../EntrySelect";
function AdjectivePicker(props: {
entryFeeder: T.EntryFeederSingleType<T.AdjectiveEntry>,
adjective: T.AdjectiveSelection | undefined,
onChange: (p: T.AdjectiveSelection | undefined) => void,
opts: T.TextOptions,
}) {
function onEntrySelect(entry: T.AdjectiveEntry | undefined) {
if (!entry) {
return props.onChange(undefined);
}
props.onChange(makeAdjectiveSelection(entry));
}
return <div style={{ maxWidth: "225px", minWidth: "125px" }}>
<h6>Adjective</h6>
<div>
<EntrySelect
value={props.adjective?.entry}
entryFeeder={props.entryFeeder}
onChange={onEntrySelect}
name="Adjective"
opts={props.opts}
/>
</div>
</div>;
}
function makeAdjectiveSelection(entry: T.AdjectiveEntry): T.AdjectiveSelection {
return {
type: "adjective",
entry: entry,
};
}
export default AdjectivePicker;

View File

@ -55,12 +55,8 @@ import EntrySelect from "../EntrySelect";
// : () => true; // : () => true;
// } // }
function NPNounPicker(props: ({ function NPNounPicker(props: {
nouns: T.NounEntry[], entryFeeder: T.EntryFeederSingleType<T.NounEntry>,
} | {
nouns: (s: string) => T.NounEntry[],
getNounByTs: (ts: number) => T.NounEntry | undefined;
}) & {
noun: T.NounSelection | undefined, noun: T.NounSelection | undefined,
onChange: (p: T.NounSelection | undefined) => void, onChange: (p: T.NounSelection | undefined) => void,
opts: T.TextOptions, opts: T.TextOptions,
@ -98,12 +94,7 @@ function NPNounPicker(props: ({
{!(props.noun && props.noun.dynamicComplement) ? <div> {!(props.noun && props.noun.dynamicComplement) ? <div>
<EntrySelect <EntrySelect
value={props.noun?.entry} value={props.noun?.entry}
{..."getNounByTs" in props ? { entryFeeder={props.entryFeeder}
getByTs: props.getNounByTs,
searchF: props.nouns
} : {
entries: props.nouns,
}}
onChange={onEntrySelect} onChange={onEntrySelect}
name="Noun" name="Noun"
opts={props.opts} opts={props.opts}

View File

@ -8,12 +8,8 @@ function makeParticipleSelection(verb: T.VerbEntry): T.ParticipleSelection {
}; };
} }
function NPParticiplePicker(props: ({ function NPParticiplePicker(props: {
verbs: T.VerbEntry[], entryFeeder: T.EntryFeederSingleType<T.VerbEntry>,
} | {
verbs: (s: string) => T.VerbEntry[],
getVerbByTs: (ts: number) => T.VerbEntry | undefined;
}) & {
participle: T.ParticipleSelection | undefined, participle: T.ParticipleSelection | undefined,
onChange: (p: T.ParticipleSelection | undefined) => void, onChange: (p: T.ParticipleSelection | undefined) => void,
opts: T.TextOptions, opts: T.TextOptions,
@ -29,12 +25,7 @@ function NPParticiplePicker(props: ({
<h6>Participle</h6> <h6>Participle</h6>
<EntrySelect <EntrySelect
value={props.participle?.verb} value={props.participle?.verb}
{..."getVerbByTs" in props ? { entryFeeder={props.entryFeeder}
getByTs: props.getVerbByTs,
searchF: props.verbs,
} : {
entries: props.verbs,
}}
onChange={onEntrySelect} onChange={onEntrySelect}
name="Pariticple" name="Pariticple"
opts={props.opts} opts={props.opts}

View File

@ -14,7 +14,7 @@ import { isSecondPerson } from "../../lib/phrase-building/vp-tools";
const npTypes: T.NPType[] = ["pronoun", "noun", "participle"]; const npTypes: T.NPType[] = ["pronoun", "noun", "participle"];
function NPPicker(props: { function NPPicker(props: {
heading?: JSX.Element, heading?: JSX.Element | string,
onChange: (nps: T.NPSelection | undefined) => void, onChange: (nps: T.NPSelection | undefined) => void,
np: T.NPSelection | undefined, np: T.NPSelection | undefined,
counterPart: T.NPSelection | T.VerbObject | undefined, counterPart: T.NPSelection | T.VerbObject | undefined,
@ -22,15 +22,8 @@ function NPPicker(props: {
opts: T.TextOptions, opts: T.TextOptions,
cantClear?: boolean, cantClear?: boolean,
is2ndPersonPicker?: boolean, is2ndPersonPicker?: boolean,
} & ({ entryFeeder: T.EntryFeeder,
nouns: (s: string) => T.NounEntry[], }) {
verbs: (s: string) => T.VerbEntry[],
getNounByTs: (ts: number) => T.NounEntry | undefined,
getVerbByTs: (ts: number) => T.VerbEntry | undefined,
} | {
nouns: T.NounEntry[],
verbs: T.VerbEntry[],
})) {
if (props.is2ndPersonPicker && ((props.np?.type !== "pronoun") || !isSecondPerson(props.np.person))) { if (props.is2ndPersonPicker && ((props.np?.type !== "pronoun") || !isSecondPerson(props.np.person))) {
throw new Error("can't use 2ndPerson NPPicker without a pronoun"); throw new Error("can't use 2ndPerson NPPicker without a pronoun");
} }
@ -66,7 +59,9 @@ function NPPicker(props: {
<div className="d-flex flex-row justify-content-between"> <div className="d-flex flex-row justify-content-between">
<div></div> <div></div>
<div> <div>
{props.heading} {typeof props.heading === "string"
? <div className="h5 text-center">{props.heading}</div>
: props.heading}
</div> </div>
<div> <div>
{npType && clearButton} {npType && clearButton}
@ -77,7 +72,7 @@ function NPPicker(props: {
<div className="h6 mr-3"> <div className="h6 mr-3">
Choose NP Choose NP
</div> </div>
{npTypes.map((npt) => <div className="mb-2"> {npTypes.map((npt) => <div key={npt} className="mb-2">
<button <button
key={npt} key={npt}
type="button" type="button"
@ -98,24 +93,14 @@ function NPPicker(props: {
/> />
: npType === "noun" : npType === "noun"
? <NounPicker ? <NounPicker
{..."getNounByTs" in props ? { entryFeeder={props.entryFeeder.nouns}
nouns: props.nouns,
getNounByTs: props.getNounByTs,
} : {
nouns: props.nouns,
}}
noun={(props.np && props.np.type === "noun") ? props.np : undefined} noun={(props.np && props.np.type === "noun") ? props.np : undefined}
onChange={props.onChange} onChange={props.onChange}
opts={props.opts} opts={props.opts}
/> />
: npType === "participle" : npType === "participle"
? <ParticiplePicker ? <ParticiplePicker
{..."getVerbByTs" in props ? { entryFeeder={props.entryFeeder.verbs}
verbs: props.verbs,
getVerbByTs: props.getVerbByTs,
} : {
verbs: props.verbs,
}}
participle={(props.np && props.np.type === "participle") ? props.np : undefined} participle={(props.np && props.np.type === "participle") ? props.np : undefined}
onChange={props.onChange} onChange={props.onChange}
opts={props.opts} opts={props.opts}

View File

@ -141,13 +141,14 @@ function NPPronounPicker({ onChange, pronoun, role, clearButton, opts, is2ndPers
<table className="table table-bordered table-sm" style={{ textAlign: "center", minWidth: "100px", tableLayout: "fixed" }}> <table className="table table-bordered table-sm" style={{ textAlign: "center", minWidth: "100px", tableLayout: "fixed" }}>
<tbody> <tbody>
{pSpec.map((rw, i) => ( {pSpec.map((rw, i) => (
<tr> <tr key={`pronounPickerRow${i}`}>
{rw.map((r, j) => { {rw.map((r, j) => {
const active = is2ndPersonPicker const active = is2ndPersonPicker
? (p.col === j) ? (p.col === j)
: (p.row === i && p.col === j); : (p.row === i && p.col === j);
const content = typeof r === "string" ? r : r[p.gender]; const content = typeof r === "string" ? r : r[p.gender];
return <td return <td
key={`pronounPickerCell${i}${j}`}
onClick={() => { onClick={() => {
handleClick(is2ndPersonPicker ? 1 : i, j); handleClick(is2ndPersonPicker ? 1 : i, j);
}} }}

View File

@ -8,11 +8,11 @@ const options = [
value: "full", value: "full",
}, },
{ {
label: <div>Kill {roleIcon.king}</div>, label: <div className="m1-2">No {roleIcon.king}</div>,
value: "noKing", value: "noKing",
}, },
{ {
label: <div>Shrink {roleIcon.servant}</div>, label: <div>Mini {roleIcon.servant}</div>,
value: "shrinkServant", value: "shrinkServant",
}, },
{ {

View File

@ -109,11 +109,11 @@ export function getRandomTense(o?: T.PerfectTense | T.VerbTense | T.ModalTense |
} }
function TensePicker(props: ({ function TensePicker(props: ({
vps: T.VPSelection, vps: T.VPSelectionState,
} | { } | {
vpsComplete: T.VPSelectionComplete, vpsComplete: T.VPSelectionComplete,
}) & { }) & {
onChange: (p: T.VPSelection) => void, onChange: (p: T.VPSelectionState) => void,
mode: "charts" | "phrases" | "quiz", mode: "charts" | "phrases" | "quiz",
}) { }) {
const [showFormula, setShowFormula] = useStickyState<boolean>(false, "showFormula"); const [showFormula, setShowFormula] = useStickyState<boolean>(false, "showFormula");
@ -228,7 +228,7 @@ function TensePicker(props: ({
return <div> return <div>
<div style={{ maxWidth: "300px", minWidth: "250px", margin: "0 auto" }}> <div style={{ maxWidth: "300px", minWidth: "250px", margin: "0 auto" }}>
<div className="d-flex flex-row justify-content-between align-items-center"> <div className="d-flex flex-row justify-content-between align-items-center">
<div className="h5">Tense:</div> <div className="h5">Verb Tense:</div>
{canHaveFormula && <div className="clickable mb-2 small" onClick={() => setShowFormula(x => !x)}> {canHaveFormula && <div className="clickable mb-2 small" onClick={() => setShowFormula(x => !x)}>
🧪 {!showFormula ? "Show" : "Hide"} Formula 🧪 {!showFormula ? "Show" : "Hide"} Formula
</div>} </div>}

View File

@ -8,7 +8,7 @@ import {
import useStickyState from "../../lib/useStickyState"; import useStickyState from "../../lib/useStickyState";
import Examples from "../Examples"; import Examples from "../Examples";
function VPDisplay({ VP, opts }: { VP: T.VPSelection, opts: T.TextOptions }) { function VPDisplay({ VP, opts }: { VP: T.VPSelectionState, opts: T.TextOptions }) {
const [form, setForm] = useStickyState<T.FormVersion>({ removeKing: false, shrinkServant: false }, "abbreviationForm"); const [form, setForm] = useStickyState<T.FormVersion>({ removeKing: false, shrinkServant: false }, "abbreviationForm");
const [OSV, setOSV] = useStickyState<boolean>(false, "includeOSV"); const [OSV, setOSV] = useStickyState<boolean>(false, "includeOSV");
const VPComplete = completeVPSelection(VP); const VPComplete = completeVPSelection(VP);
@ -21,7 +21,7 @@ function VPDisplay({ VP, opts }: { VP: T.VPSelection, opts: T.TextOptions }) {
</div>; </div>;
} }
const result = compileVP(renderVP(VPComplete), { ...form, OSV }); const result = compileVP(renderVP(VPComplete), { ...form, OSV });
return <div className="text-center"> return <div className="text-center mt-1">
{VP.verb.transitivity === "transitive" && <div className="form-check mb-2"> {VP.verb.transitivity === "transitive" && <div className="form-check mb-2">
<input <input
className="form-check-input" className="form-check-input"

View File

@ -7,7 +7,7 @@ import {
isInvalidSubjObjCombo, isInvalidSubjObjCombo,
} from "../../lib/phrase-building/vp-tools"; } from "../../lib/phrase-building/vp-tools";
import * as T from "../../types"; import * as T from "../../types";
import ChartDisplay from "./ChartDisplay"; import ChartDisplay from "./VPChartDisplay";
import useStickyState from "../../lib/useStickyState"; import useStickyState from "../../lib/useStickyState";
import { makeVPSelectionState } from "./verb-selection"; import { makeVPSelectionState } from "./verb-selection";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@ -41,16 +41,9 @@ export function VPExplorer(props: {
verb: T.VerbEntry, verb: T.VerbEntry,
opts: T.TextOptions, opts: T.TextOptions,
handleLinkClick: ((ts: number) => void) | "none", handleLinkClick: ((ts: number) => void) | "none",
} & ({ entryFeeder: T.EntryFeeder,
nouns: T.NounEntry[], }) {
verbs: T.VerbEntry[], const [vps, setVps] = useStickyState<T.VPSelectionState>(
} | {
nouns: (s: string) => T.NounEntry[],
verbs: (s: string) => T.VerbEntry[],
getNounByTs: (ts: number) => T.NounEntry | undefined,
getVerbByTs: (ts: number) => T.VerbEntry | undefined,
})) {
const [vps, setVps] = useStickyState<T.VPSelection>(
savedVps => makeVPSelectionState(props.verb, savedVps), savedVps => makeVPSelectionState(props.verb, savedVps),
"vpsState5", "vpsState5",
); );
@ -115,12 +108,6 @@ export function VPExplorer(props: {
} }
return <div className="mt-3" style={{ maxWidth: "950px"}}> return <div className="mt-3" style={{ maxWidth: "950px"}}>
<VerbPicker <VerbPicker
{..."getNounByTs" in props ? {
getVerbByTs: props.getVerbByTs,
verbs: props.verbs,
} : {
verbs: props.verbs,
}}
vps={vps} vps={vps}
onChange={quizLock(setVps)} onChange={quizLock(setVps)}
opts={props.opts} opts={props.opts}
@ -150,15 +137,7 @@ export function VPExplorer(props: {
heading={roles.king === "subject" heading={roles.king === "subject"
? <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "king", item: "subject" })}>Subject {roleIcon.king}</div> ? <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "king", item: "subject" })}>Subject {roleIcon.king}</div>
: <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "servant", item: "subject" })}>Subject {roleIcon.servant}</div>} : <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "servant", item: "subject" })}>Subject {roleIcon.servant}</div>}
{..."getNounByTs" in props ? { entryFeeder={props.entryFeeder}
getNounByTs: props.getNounByTs,
getVerbByTs: props.getVerbByTs,
nouns: props.nouns,
verbs: props.verbs,
} : {
nouns: props.nouns,
verbs: props.verbs,
}}
role={(isPast && vps.verb.transitivity !== "intransitive") role={(isPast && vps.verb.transitivity !== "intransitive")
? "ergative" ? "ergative"
: "subject" : "subject"
@ -177,15 +156,7 @@ export function VPExplorer(props: {
heading={roles.king === "object" heading={roles.king === "object"
? <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "king", item: "object" })}>Object {roleIcon.king}</div> ? <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "king", item: "object" })}>Object {roleIcon.king}</div>
: <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "servant", item: "object" })}>Object {roleIcon.servant}</div>} : <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "servant", item: "object" })}>Object {roleIcon.servant}</div>}
{..."getNounByTs" in props ? { entryFeeder={props.entryFeeder}
getNounByTs: props.getNounByTs,
getVerbByTs: props.getVerbByTs,
nouns: props.nouns,
verbs: props.verbs,
} : {
nouns: props.nouns,
verbs: props.verbs,
}}
role="object" role="object"
np={vps.verb.object} np={vps.verb.object}
counterPart={vps.subject} counterPart={vps.subject}

View File

@ -14,7 +14,7 @@ import playAudio from "../../lib/play-audio";
import TensePicker from "./TensePicker"; import TensePicker from "./TensePicker";
import Keyframes from "../Keyframes"; import Keyframes from "../Keyframes";
import energyDrink from "./energy-drink.jpg"; import energyDrink from "./energy-drink.jpg";
import { flattenLengths } from "../../lib/phrase-building/compile-vp"; import { flattenLengths } from "../../lib/phrase-building/segment";
import { concatPsString } from "../../lib/p-text-helpers"; import { concatPsString } from "../../lib/p-text-helpers";
import { isImperativeTense } from "../../lib/type-predicates"; import { isImperativeTense } from "../../lib/type-predicates";
@ -56,7 +56,7 @@ type MixType = "NPs" | "tenses" | "both";
function VPExplorerQuiz(props: { function VPExplorerQuiz(props: {
opts: T.TextOptions, opts: T.TextOptions,
vps: T.VPSelection, vps: T.VPSelectionState,
}) { }) {
const startingQs = tickQuizState(completeVPs(props.vps)); const startingQs = tickQuizState(completeVPs(props.vps));
const [quizState, setQuizState] = useState<QuizState>(startingQs); const [quizState, setQuizState] = useState<QuizState>(startingQs);
@ -370,7 +370,7 @@ function getOptionFromResult(r: {
return ps[0]; return ps[0];
} }
function completeVPs(vps: T.VPSelection): T.VPSelectionComplete { function completeVPs(vps: T.VPSelectionState): T.VPSelectionComplete {
const oldSubj = vps.subject?.type === "pronoun" const oldSubj = vps.subject?.type === "pronoun"
? vps.subject.person ? vps.subject.person
: undefined; : undefined;

View File

@ -9,8 +9,8 @@ import CompoundDisplay from "./CompoundDisplay";
// TODO: dark on past tense selecitons // TODO: dark on past tense selecitons
function VerbPicker(props: { function VerbPicker(props: {
vps: T.VPSelection, vps: T.VPSelectionState,
onChange: (p: T.VPSelection) => void, onChange: (p: T.VPSelectionState) => void,
opts: T.TextOptions, opts: T.TextOptions,
handleLinkClick: ((ts: number) => void) | "none", handleLinkClick: ((ts: number) => void) | "none",
}) { }) {

View File

@ -6,8 +6,8 @@ import { getVerbInfo } from "../../lib/verb-info";
export function makeVPSelectionState( export function makeVPSelectionState(
verb: T.VerbEntry, verb: T.VerbEntry,
os?: T.VPSelection, os?: T.VPSelectionState,
): T.VPSelection { ): T.VPSelectionState {
const info = getVerbInfo(verb.entry, verb.complement); const info = getVerbInfo(verb.entry, verb.complement);
const subject = (os?.verb.voice === "passive" && info.type === "dynamic compound") const subject = (os?.verb.voice === "passive" && info.type === "dynamic compound")
? makeNounSelection(info.objComplement.entry as T.NounEntry, true) ? makeNounSelection(info.objComplement.entry as T.NounEntry, true)

View File

@ -58,7 +58,7 @@ export function accentPastParticiple(s: T.PsString): T.PsString {
} }
export function splitUpSyllables(f: string): string[] { export function splitUpSyllables(f: string): string[] {
return f.match(/ |([^a|e|i|o|u| ]*(aa|a|ey|ee|e|oo|o|i|u)[^a|e|i|o|u| ]*)/ig) || [] as string[]; return f.match(/ |([^a|á|e|é|i|o|ó|u| ]*(aa|áa|a|á|ey|éy|ee|ée|e|é|oo|óo|o|ó|i|u)[^a|á|e|é|i|í|o|u| ]*)/ig) || [] as string[];
} }
export function countSyllables(f: T.PsString | string): number { export function countSyllables(f: T.PsString | string): number {
@ -110,7 +110,11 @@ function accentSyllable(s: string): string {
export function removeAccents(s: T.PsString): T.PsString; export function removeAccents(s: T.PsString): T.PsString;
export function removeAccents(s: string): string; export function removeAccents(s: string): string;
export function removeAccents(s: T.PsString | string): T.PsString | string { export function removeAccents(s: T.PsString[]): T.PsString[];
export function removeAccents(s: T.PsString | string | T.PsString[]): T.PsString | string | T.PsString[] {
if (Array.isArray(s)) {
return s.map(t => removeAccents(t));
}
if (typeof s !== "string") { if (typeof s !== "string") {
return { return {
...s, ...s,

View File

@ -81,7 +81,7 @@ export function chooseParticipleInflection(
return pPartInfs; // already just one thing return pPartInfs; // already just one thing
} }
export function getPersonNumber(gender: "masc" | "fem", number: "singular" | "plural"): T.Person { export function getPersonNumber(gender: T.Gender, number: T.NounNumber): T.Person {
const base = gender === "masc" ? 4 : 5; const base = gender === "masc" ? 4 : 5;
return base + (number === "singular" ? 0 : 6); return base + (number === "singular" ? 0 : 6);
} }
@ -129,10 +129,14 @@ export function getAuxTransitivity(trans: T.Transitivity): "transitive" | "intra
return trans === "intransitive" ? "intransitive" : "transitive"; return trans === "intransitive" ? "intransitive" : "transitive";
} }
export function personGender(person: T.Person): "masc" | "fem" { export function personGender(person: T.Person): T.Gender {
return person % 2 === 0 ? "masc" : "fem"; return person % 2 === 0 ? "masc" : "fem";
} }
export function personNumber(person: T.Person): T.NounNumber {
return personIsPlural(person) ? "plural" : "singular";
}
export function personIsPlural(person: T.Person): boolean { export function personIsPlural(person: T.Person): boolean {
return person > 5; return person > 5;
} }

View File

@ -126,6 +126,24 @@ const adjectives: Array<{
}, },
}, },
}, },
// regular adjective ending in a consonant with an accent already
{
in: {"ts":1527818704,"i":352,"p":"ارت","f":"arát","g":"arat","e":"wide, spacious, extensive","c":"adj."},
out: {
inflections: {
masc: [
[{p: "ارت", f: "arát"}],
[{p: "ارت", f: "arát"}],
[{p: "ارتو", f: "aráto"}],
],
fem: [
[{p: "ارته", f: "aráta"}],
[{p: "ارتې", f: "aráte"}],
[{p: "ارتو", f: "aráto"}],
],
},
},
},
{ {
in: { in: {
ts: 1527812862, ts: 1527812862,

View File

@ -49,7 +49,7 @@ export function inflectWord(word: T.DictionaryEntry): T.InflectorOutput {
}; };
} }
if (w.c && w.c.includes("pl.")) { if (w.c && w.c.includes("pl.")) {
return handlePluralNoun(w); return handlePluralNounOrAdj(w);
} }
if (w.c && (w.c.includes("adj.") || w.c.includes("unisex") || w.c.includes("num"))) { if (w.c && (w.c.includes("adj.") || w.c.includes("unisex") || w.c.includes("num"))) {
return handleUnisexWord(w); return handleUnisexWord(w);
@ -104,7 +104,7 @@ function handleUnisexWord(word: T.DictionaryEntryNoFVars): T.InflectorOutput {
return false; return false;
} }
function handlePluralNoun(w: T.DictionaryEntryNoFVars): T.InflectorOutput { function handlePluralNounOrAdj(w: T.DictionaryEntryNoFVars): T.InflectorOutput {
if (!w.c || !w.c.includes("n.")) return false; if (!w.c || !w.c.includes("n.")) return false;
const plurals = makePlural(w); const plurals = makePlural(w);
if (w.noInf) { if (w.noInf) {
@ -261,7 +261,7 @@ function inflectEmphasizedYeyUnisex(p: string, f: string): T.UnisexInflections {
} }
function inflectConsonantEndingUnisex(p: string, f: string): T.UnisexInflections { function inflectConsonantEndingUnisex(p: string, f: string): T.UnisexInflections {
const fSyls = splitUpSyllables(f); const fSyls = splitUpSyllables(removeAccents(f));
const iBase = fSyls.length === 1 const iBase = fSyls.length === 1
? makePsString(p, accentFSylsOnNFromEnd(fSyls, 0)) ? makePsString(p, accentFSylsOnNFromEnd(fSyls, 0))
: makePsString(p, f); : makePsString(p, f);

View File

@ -0,0 +1,89 @@
import * as T from "../../types";
import * as grammarUnits from "../grammar-units";
import {
removeDuplicates,
} from "./vp-tools";
import {
combineSegments,
makeSegment,
putKidsInKidsSection,
Segment,
flattenLengths,
} from "./segment";
import { removeAccents } from "../accent-helpers";
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 [] };
export function compileEP(EP: T.EPRendered, form: T.FormVersion, combineLengths?: true): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string [] } {
const { kids, NPs } = getSegmentsAndKids(EP, form);
const equative = EP.equative.ps;
const psResult = compilePs({
NPs,
kids,
equative,
negative: EP.equative.negative,
});
return {
ps: combineLengths ? flattenLengths(psResult) : psResult,
e: compileEnglish(EP),
};
}
function getSegmentsAndKids(EP: T.EPRendered, form: T.FormVersion): { kids: Segment[], NPs: Segment[] } {
function ifNotRemoved(s: Segment, role: "subject" | "predicate"): Segment[] {
if (form.removeKing && EP.king === role) {
return [];
}
return [s];
}
const subject = makeSegment(EP.subject.ps);
const predicate = makeSegment(EP.predicate.ps);
return {
kids: EP.equative.hasBa
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])]
: [],
NPs: [
...ifNotRemoved(subject, "subject"),
...ifNotRemoved(predicate, "predicate"),
],
};
}
function compilePs({ NPs, kids, equative, negative }: {
NPs: Segment[],
kids: Segment[],
equative: T.SingleOrLengthOpts<T.PsString[]>,
negative: boolean,
}): T.SingleOrLengthOpts<T.PsString[]> {
if ("long" in equative) {
return {
long: compilePs({ NPs, kids, equative: equative.long, negative }) as T.PsString[],
short: compilePs({ NPs, kids, equative: equative.short, negative }) as T.PsString[],
};
}
const allSegments = putKidsInKidsSection([
...NPs,
...negative ? [
makeSegment({ p: "نه", f: "nú" }),
makeSegment(removeAccents(equative))
] : [
makeSegment(equative),
],
], kids);
return removeDuplicates(combineSegments(allSegments, "spaces"));
}
function compileEnglish(EP: T.EPRendered): string[] | undefined {
function insertEWords(e: string, { subject, predicate }: { subject: string, predicate: string }): string {
return e.replace("$SUBJ", subject).replace("$PRED", predicate || "");
}
const engSubj = EP.subject.e || undefined;
const engPred = EP.predicate.e || undefined;
// require all English parts for making the English phrase
return (EP.englishBase && engSubj && engPred)
? EP.englishBase.map(e => insertEWords(e, {
subject: engSubj,
predicate: engPred,
}))
: undefined;
}

View File

@ -2,7 +2,14 @@ import * as T from "../../types";
import { import {
concatPsString, concatPsString,
} from "../p-text-helpers"; } from "../p-text-helpers";
import { makePsString } from "../accent-and-ps-utils"; import {
Segment,
makeSegment,
flattenLengths,
combineSegments,
splitOffLeapfrogWord,
putKidsInKidsSection,
} from "./segment";
import { import {
removeAccents, removeAccents,
} from "../accent-helpers"; } from "../accent-helpers";
@ -116,19 +123,6 @@ function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NP
}; };
} }
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,
];
}
function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[], V: T.VerbRendered): Segment[][] { function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[], V: T.VerbRendered): Segment[][] {
const hasLeapfrog = isPerfectTense(V.tense) || isModalTense(V.tense); const hasLeapfrog = isPerfectTense(V.tense) || isModalTense(V.tense);
const rest = (() => { const rest = (() => {
@ -299,91 +293,3 @@ function compileEnglish(VP: T.VPRendered): string[] | undefined {
: undefined; : undefined;
} }
// SEGMENT TOOLS
// TODO: PULL OUT FOR SHARING ACROSS COMPILE EP ETC?
type SegmentDescriptions = {
isVerbHead?: boolean,
isOoOrWaaHead?: boolean,
isVerbRest?: boolean,
isMiniPronoun?: boolean,
isKid?: boolean,
// TODO: Simplify to just isKidAfterHead?
isKidBetweenHeadAndRest?: boolean,
isNu?: boolean,
isBa?: boolean,
}
type SDT = keyof SegmentDescriptions;
type Segment = { ps: T.PsString[] } & SegmentDescriptions & {
adjust: (o: { ps?: T.PsString | T.PsString[] | ((ps: T.PsString) => T.PsString), desc?: SDT[] }) => Segment,
};
function makeSegment(
ps: T.PsString | T.PsString[],
options?: (keyof SegmentDescriptions)[],
): Segment {
return {
ps: Array.isArray(ps) ? ps : [ps],
...options && options.reduce((all, curr) => ({
...all,
[curr]: true,
}), {}),
adjust: function(o): Segment {
return {
...this,
...o.ps ? {
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,
}), {}),
};
},
};
}
function combineSegments(loe: (Segment | " " | "" | T.PsString)[]): 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).flatMap(r => (
(typeof first === "object" && "ps" in first)
? first.ps.map(f => concatPsString(f, r))
: [concatPsString(first, r)]
)
);
}
export function flattenLengths(r: T.SingleOrLengthOpts<T.PsString[]>): T.PsString[] {
if ("long" in r) {
return Object.values(r).flat();
}
return r;
}
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

@ -0,0 +1,163 @@
import * as T from "../../types";
import * as g from "../grammar-units";
import {
getPersonFromNP,
} from "./vp-tools";
import { renderNPSelection } from "./render-np";
import { getPersonFromVerbForm } from "../../lib/misc-helpers";
import { getVerbBlockPosFromPerson } from "../misc-helpers";
import { getEnglishWord } from "../get-english-word";
import { isUnisexSet, psStringFromEntry } from "../p-text-helpers";
import { inflectWord } from "../pashto-inflector";
import { personGender, personIsPlural } from "../../library";
import { isLocativeAdverbEntry } from "../type-predicates";
export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
const kingPerson = (EP.subject.type === "pronoun")
? getPersonFromNP(EP.subject)
: EP.predicate.type === "NP"
? getPersonFromNP(EP.predicate.selection)
: getPersonFromNP(EP.subject);
return {
type: "EPRendered",
king: EP.predicate.type === "Complement" ? "subject" : "predicate",
subject: renderNPSelection(EP.subject, false, false, "subject"),
predicate: EP.predicate.type === "NP"
? renderNPSelection(EP.predicate.selection, false, true, "subject")
: renderEqCompSelection(EP.predicate.selection, kingPerson),
equative: renderEquative(EP.equative, kingPerson),
englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative),
};
}
export function getEquativeForm(tense: T.EquativeTense): { hasBa: boolean, form: T.SingleOrLengthOpts<T.VerbBlock> } {
const hasBa = (tense === "future" || tense === "wouldBe");
const baseTense = (tense === "future")
? "habitual"
: tense === "wouldBe"
? "past"
: tense;
return {
hasBa,
form: g.equativeEndings[baseTense],
}
}
function renderEquative(es: T.EquativeSelection, person: T.Person): T.EquativeRendered {
const { form, hasBa } = getEquativeForm(es.tense)
const ps = getPersonFromVerbForm(form, person);
return {
...es,
person,
hasBa,
ps,
};
}
function renderEqCompSelection(s: T.EqCompSelection, person: T.Person): T.Rendered<T.EqCompSelection> {
const e = getEnglishWord(s.entry);
if (!e || typeof e !== "string") {
console.log(e);
throw new Error("error getting english for compliment");
}
if (isLocativeAdverbEntry(s.entry)) {
return {
type: "loc. adv.",
entry: s.entry,
ps: [psStringFromEntry(s.entry)],
e,
inflected: false,
person,
};
}
if (s.type === "adjective") {
const infs = inflectWord(s.entry);
if (!infs) return {
type: "adjective",
entry: s.entry,
ps: [psStringFromEntry(s.entry)],
e,
inflected: false,
person,
}
if (!infs.inflections || !isUnisexSet(infs.inflections)) {
throw new Error("error getting inflections for adjective, looks like a noun's inflections");
}
return {
type: "adjective",
entry: s.entry,
ps: chooseInflection(infs.inflections, person),
e,
inflected: false,
person,
};
}
throw new Error("invalid EqCompSelection");
}
const equativeBuilders: Record<T.EquativeTense, (p: T.Person, n: boolean) => string[]> = {
present: (p, n) => {
return [
`$SUBJ ${getEnglishConj(p, g.englishEquative.present)}${not(n)} $PRED`,
];
},
habitual: (p, n) => {
return [
`$SUBJ ${getEnglishConj(p, g.englishEquative.present)}${not(n)} $PRED`,
`$SUBJ tend${isThirdPersonSing(p) ? "s" : ""}${not(n)} to be $PRED`,
];
},
subjunctive: (p, n) => {
return [
`$SUBJ ${getEnglishConj(p, g.englishEquative.present)}${not(n)} $PRED`,
`...that $SUBJ ${getEnglishConj(p, g.englishEquative.present)}${not(n)} $PRED`,
`$SUBJ should${not(n)} be $PRED`,
];
},
future: (p, n) => {
return [
`$SUBJ will${not(n)} be $PRED`,
`I betcha $SUBJ ${getEnglishConj(p, g.englishEquative.present)}${not(n)} $PRED`,
];
},
past: (p, n) => {
return [
`$SUBJ ${getEnglishConj(p, g.englishEquative.past)}${not(n)} $PRED`,
];
},
wouldBe: (p, n) => {
return [
`$SUBJ would ${n ? "not " : ""}be $PRED`,
`$SUBJ would ${n ? "not " : ""}have been $PRED`,
`$SUBJ ${getEnglishConj(p, g.englishEquative.past)} probably${not(n)} $PRED`,
];
},
pastSubjunctive: () => {
return [
`$SUBJ should have been $PRED`,
`(that) $SUBJ were $PRED`,
];
},
}
function isThirdPersonSing(p: T.Person): boolean {
return p === T.Person.ThirdSingMale || p === T.Person.ThirdPlurFemale;
}
function not(n: boolean): string {
return n ? "not " : "";
}
function getEnglishConj(p: T.Person, e: string | T.EnglishBlock): string {
if (typeof e === "string") {
return e;
}
const [row, col] = getVerbBlockPosFromPerson(p);
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];
}

View File

@ -0,0 +1,125 @@
import * as T from "../../types";
import { inflectWord } from "../pashto-inflector";
import * as grammarUnits from "../grammar-units";
import {
getVerbBlockPosFromPerson,
getPersonNumber,
} from "../misc-helpers";
import {
concatPsString,
psStringFromEntry,
} from "../p-text-helpers";
import { parseEc } from "../misc-helpers";
import { getEnglishWord } from "../get-english-word";
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" {
if (typeof NP !== "object") {
if (role !== "object") {
throw new Error("ObjectNP only allowed for objects");
}
return NP;
}
if (NP.type === "noun") {
return renderNounSelection(NP, inflected);
}
if (NP.type === "pronoun") {
return renderPronounSelection(NP, inflected, inflectEnglish);
}
if (NP.type === "participle") {
return renderParticipleSelection(NP, inflected)
}
throw new Error("unknown NP type");
};
function renderNounSelection(n: T.NounSelection, inflected: boolean): T.Rendered<T.NounSelection> {
const english = getEnglishFromNoun(n.entry, n.number);
const pashto = ((): T.PsString[] => {
const infs = inflectWord(n.entry);
const ps = n.number === "singular"
? getInf(infs, "inflections", n.gender, false, inflected)
: [
...getInf(infs, "plural", n.gender, true, inflected),
...getInf(infs, "arabicPlural", n.gender, true, inflected),
...getInf(infs, "inflections", n.gender, true, inflected),
];
return ps.length > 0
? ps
: [psStringFromEntry(n.entry)];
})();
return {
...n,
person: getPersonNumber(n.gender, n.number),
inflected,
ps: pashto,
e: english,
};
}
function renderPronounSelection(p: T.PronounSelection, inflected: boolean, englishInflected: boolean): T.Rendered<T.PronounSelection> {
const [row, col] = getVerbBlockPosFromPerson(p.person);
return {
...p,
inflected,
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> {
return {
...p,
inflected,
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),
};
}
function getInf(infs: T.InflectorOutput, t: "plural" | "arabicPlural" | "inflections", gender: T.Gender, plural: boolean, inflected: boolean): T.PsString[] {
// TODO: make this safe!!
// @ts-ignore
if (infs && t in infs && infs[t] !== undefined && gender in infs[t] && infs[t][gender] !== undefined) {
// @ts-ignore
const iset = infs[t][gender] as T.InflectionSet;
const inflectionNumber = (inflected ? 1 : 0) + ((t === "inflections" && plural) ? 1 : 0);
return iset[inflectionNumber];
}
return [];
}
function getEnglishParticiple(entry: T.DictionaryEntry): string {
if (!entry.ec) {
console.log("errored participle");
console.log(entry);
throw new Error("no english information for participle");
}
const ec = parseEc(entry.ec);
const participle = ec[2];
return (entry.ep)
? `${participle} ${entry.ep}`
: participle;
}
function getEnglishFromNoun(entry: T.DictionaryEntry, number: T.NounNumber): string {
const articles = {
singular: "(a/the)",
plural: "(the)",
};
const article = articles[number];
function addArticle(s: string) {
return `${article} ${s}`;
}
const e = getEnglishWord(entry);
if (!e) throw new Error(`unable to get english from subject ${entry.f} - ${entry.ts}`);
if (typeof e === "string") return ` ${e}`;
if (number === "plural") return addArticle(e.plural);
if (!e.singular || e.singular === undefined) {
throw new Error(`unable to get english from subject ${entry.f} - ${entry.ts}`);
}
return addArticle(e.singular);
}

View File

@ -1,17 +1,10 @@
import * as T from "../../types"; import * as T from "../../types";
import { parseEc } from "../../lib/misc-helpers";
import { inflectWord } from "../pashto-inflector";
import { getEnglishWord } from "../get-english-word";
import * as grammarUnits from "../grammar-units";
import { import {
getVerbBlockPosFromPerson, getVerbBlockPosFromPerson,
getPersonNumber,
} from "../misc-helpers"; } from "../misc-helpers";
import { conjugateVerb } from "../verb-conjugation"; import { conjugateVerb } from "../verb-conjugation";
import { import {
concatPsString,
hasBaParticle, hasBaParticle,
psStringFromEntry,
getLong, getLong,
isImperativeBlock, isImperativeBlock,
} from "../p-text-helpers"; } from "../p-text-helpers";
@ -28,6 +21,7 @@ import {
} from "../type-predicates"; } from "../type-predicates";
import { renderEnglishVPBase } from "./english-vp-rendering"; import { renderEnglishVPBase } from "./english-vp-rendering";
import { personGender } from "../../lib/misc-helpers"; import { personGender } from "../../lib/misc-helpers";
import { renderNPSelection } from "./render-np";
// TODO: ISSUE GETTING SPLIT HEAD NOT MATCHING WITH FUTURE VERBS // TODO: ISSUE GETTING SPLIT HEAD NOT MATCHING WITH FUTURE VERBS
@ -67,72 +61,6 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
}; };
} }
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" {
if (typeof NP !== "object") {
if (role !== "object") {
throw new Error("ObjectNP only allowed for objects");
}
return NP;
}
if (NP.type === "noun") {
return renderNounSelection(NP, inflected);
}
if (NP.type === "pronoun") {
return renderPronounSelection(NP, inflected, inflectEnglish);
}
if (NP.type === "participle") {
return renderParticipleSelection(NP, inflected)
}
throw new Error("unknown NP type");
};
function renderNounSelection(n: T.NounSelection, inflected: boolean): T.Rendered<T.NounSelection> {
const english = getEnglishFromNoun(n.entry, n.number);
const pashto = ((): T.PsString[] => {
const infs = inflectWord(n.entry);
const ps = n.number === "singular"
? getInf(infs, "inflections", n.gender, false, inflected)
: [
...getInf(infs, "plural", n.gender, true, inflected),
...getInf(infs, "arabicPlural", n.gender, true, inflected),
...getInf(infs, "inflections", n.gender, true, inflected),
];
return ps.length > 0
? ps
: [psStringFromEntry(n.entry)];
})();
return {
...n,
person: getPersonNumber(n.gender, n.number),
inflected,
ps: pashto,
e: english,
};
}
function renderPronounSelection(p: T.PronounSelection, inflected: boolean, englishInflected: boolean): T.Rendered<T.PronounSelection> {
const [row, col] = getVerbBlockPosFromPerson(p.person);
return {
...p,
inflected,
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> {
return {
...p,
inflected,
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),
};
}
function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): T.VerbRendered { function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): T.VerbRendered {
const v = vs.dynAuxVerb || vs.verb; const v = vs.dynAuxVerb || vs.verb;
const conjugations = conjugateVerb(v.entry, v.complement); const conjugations = conjugateVerb(v.entry, v.complement);
@ -267,51 +195,6 @@ function getMatrixBlock<U>(f: {
return f[personToLabel(person)]; return f[personToLabel(person)];
} }
function getEnglishParticiple(entry: T.DictionaryEntry): string {
if (!entry.ec) {
console.log("errored participle");
console.log(entry);
throw new Error("no english information for participle");
}
const ec = parseEc(entry.ec);
const participle = ec[2];
return (entry.ep)
? `${participle} ${entry.ep}`
: participle;
}
function getEnglishFromNoun(entry: T.DictionaryEntry, number: T.NounNumber): string {
const articles = {
singular: "(a/the)",
plural: "(the)",
};
const article = articles[number];
function addArticle(s: string) {
return `${article} ${s}`;
}
const e = getEnglishWord(entry);
if (!e) throw new Error(`unable to get english from subject ${entry.f} - ${entry.ts}`);
if (typeof e === "string") return ` ${e}`;
if (number === "plural") return addArticle(e.plural);
if (!e.singular || e.singular === undefined) {
throw new Error(`unable to get english from subject ${entry.f} - ${entry.ts}`);
}
return addArticle(e.singular);
}
function getInf(infs: T.InflectorOutput, t: "plural" | "arabicPlural" | "inflections", gender: T.Gender, plural: boolean, inflected: boolean): T.PsString[] {
// TODO: make this safe!!
// @ts-ignore
if (infs && t in infs && infs[t] !== undefined && gender in infs[t] && infs[t][gender] !== undefined) {
// @ts-ignore
const iset = infs[t][gender] as T.InflectionSet;
const inflectionNumber = (inflected ? 1 : 0) + ((t === "inflections" && plural) ? 1 : 0);
return iset[inflectionNumber];
}
return [];
}
export function getKingAndServant(isPast: boolean, isTransitive: boolean): export function getKingAndServant(isPast: boolean, isTransitive: boolean):
{ king: "subject", servant: "object" } | { king: "subject", servant: "object" } |
{ king: "object", servant: "subject" } | { king: "object", servant: "subject" } |

View File

@ -0,0 +1,110 @@
import * as T from "../../types";
import {
makePsString,
} from "../accent-and-ps-utils";
import {
concatPsString,
} from "../p-text-helpers";
// SEGMENT TOOLS
// TODO: PULL OUT FOR SHARING ACROSS COMPILE EP ETC?
type SegmentDescriptions = {
isVerbHead?: boolean,
isOoOrWaaHead?: boolean,
isVerbRest?: boolean,
isMiniPronoun?: boolean,
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(
ps: T.PsString | T.PsString[],
options?: (keyof SegmentDescriptions)[],
): Segment {
return {
ps: Array.isArray(ps) ? ps : [ps],
...options && options.reduce((all, curr) => ({
...all,
[curr]: true,
}), {}),
adjust: function(o): Segment {
return {
...this,
...o.ps ? {
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[] {
if ("long" in r) {
return Object.values(r).flat();
}
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

@ -193,9 +193,9 @@ export function removeDuplicates(psv: T.PsString[]): T.PsString[] {
)); ));
} }
export function switchSubjObj(vps: T.VPSelection): T.VPSelection; export function switchSubjObj(vps: T.VPSelectionState): T.VPSelectionState;
export function switchSubjObj(vps: T.VPSelectionComplete): T.VPSelectionComplete; export function switchSubjObj(vps: T.VPSelectionComplete): T.VPSelectionComplete;
export function switchSubjObj(vps: T.VPSelection | T.VPSelectionComplete): T.VPSelection | T.VPSelectionComplete { export function switchSubjObj(vps: T.VPSelectionState | T.VPSelectionComplete): T.VPSelectionState | T.VPSelectionComplete {
if ("tenseCategory" in vps.verb) { if ("tenseCategory" in vps.verb) {
if (!vps.subject || !(typeof vps.verb.object === "object") || (vps.verb.tenseCategory === "imperative")) { if (!vps.subject || !(typeof vps.verb.object === "object") || (vps.verb.tenseCategory === "imperative")) {
return vps; return vps;
@ -222,7 +222,7 @@ export function switchSubjObj(vps: T.VPSelection | T.VPSelectionComplete): T.VPS
}; };
} }
export function completeVPSelection(vps: T.VPSelection): T.VPSelectionComplete | undefined { export function completeVPSelection(vps: T.VPSelectionState): T.VPSelectionComplete | undefined {
if (vps.subject === undefined) { if (vps.subject === undefined) {
return undefined; return undefined;
} }
@ -261,7 +261,7 @@ export function isThirdPerson(p: T.Person): boolean {
); );
} }
export function ensure2ndPersSubjPronounAndNoConflict(vps: T.VPSelection): T.VPSelection { export function ensure2ndPersSubjPronounAndNoConflict(vps: T.VPSelectionState): T.VPSelectionState {
console.log("checking more...", vps); console.log("checking more...", vps);
const subjIs2ndPerson = (vps.subject?.type === "pronoun") && isSecondPerson(vps.subject.person); const subjIs2ndPerson = (vps.subject?.type === "pronoun") && isSecondPerson(vps.subject.person);
const objIs2ndPerson = (typeof vps.verb.object === "object") const objIs2ndPerson = (typeof vps.verb.object === "object")

View File

@ -14,12 +14,12 @@ export function isAdjectiveEntry(e: T.Entry | T.DictionaryEntry): e is T.Adjecti
return !!e.c?.includes("adj.") && !isNounEntry(e); return !!e.c?.includes("adj.") && !isNounEntry(e);
} }
export function isAdverbEntry(e: T.Entry): e is T.AdverbEntry { export function isAdverbEntry(e: T.Entry | T.DictionaryEntry): e is T.AdverbEntry {
if ("entry" in e) return false; if ("entry" in e) return false;
return !!e.c?.includes("adv."); return !!e.c?.includes("adv.");
} }
export function isLocativeAdverbEntry(e: T.Entry): e is T.LocativeAdverbEntry { export function isLocativeAdverbEntry(e: T.Entry | T.DictionaryEntry): e is T.LocativeAdverbEntry {
return isAdverbEntry(e) && e.c.includes("loc. adv."); return isAdverbEntry(e) && e.c.includes("loc. adv.");
} }

View File

@ -95,6 +95,7 @@ import {
personIsPlural, personIsPlural,
personGender, personGender,
parseEc, parseEc,
personNumber,
} from "./lib/misc-helpers"; } from "./lib/misc-helpers";
import { import {
simplifyPhonetics, simplifyPhonetics,
@ -170,6 +171,7 @@ export {
isInvalidSubjObjCombo, isInvalidSubjObjCombo,
randomSubjObj, randomSubjObj,
shuffleArray, shuffleArray,
personNumber,
// protobuf helpers // protobuf helpers
readDictionary, readDictionary,
writeDictionary, writeDictionary,

File diff suppressed because one or more lines are too long

View File

@ -503,7 +503,7 @@ export type Words = {
adverbs: AdverbEntry[], adverbs: AdverbEntry[],
} }
// TODO: make this Rendered<VPSelection> with recursive Rendered<> // TODO: make this Rendered<VPSelectionComplete> with recursive Rendered<>
export type VPRendered = { export type VPRendered = {
type: "VPRendered", type: "VPRendered",
king: "subject" | "object", king: "subject" | "object",
@ -533,7 +533,7 @@ export type ModalTense = `${VerbTense}Modal`;
export type ImperativeTense = `${Aspect}Imperative`; export type ImperativeTense = `${Aspect}Imperative`;
export type Tense = EquativeTense | VerbTense | PerfectTense | ModalTense | ImperativeTense; export type Tense = EquativeTense | VerbTense | PerfectTense | ModalTense | ImperativeTense;
export type VPSelection = { export type VPSelectionState = {
subject: NPSelection | undefined, subject: NPSelection | undefined,
verb: VerbSelection, verb: VerbSelection,
}; };
@ -611,6 +611,16 @@ export type NounSelection = {
changeNumber?: (number: NounNumber) => NounSelection, changeNumber?: (number: NounNumber) => NounSelection,
}; };
export type AdjectiveSelection = {
type: "adjective",
entry: AdjectiveEntry,
}
export type LocativeAdverbSelection = {
type: "loc. adv.",
entry: LocativeAdverbEntry,
}
// take an argument for subject/object in rendering English // take an argument for subject/object in rendering English
export type PronounSelection = { export type PronounSelection = {
type: "pronoun", type: "pronoun",
@ -630,7 +640,7 @@ export type ReplaceKey<T, K extends string, R> = T extends Record<K, unknown> ?
export type FormVersion = { removeKing: boolean, shrinkServant: boolean }; export type FormVersion = { removeKing: boolean, shrinkServant: boolean };
export type Rendered<T extends NPSelection> = ReplaceKey< export type Rendered<T extends NPSelection | EqCompSelection> = ReplaceKey<
Omit<T, "changeGender" | "changeNumber" | "changeDistance">, Omit<T, "changeGender" | "changeNumber" | "changeDistance">,
"e", "e",
string string
@ -641,3 +651,67 @@ export type Rendered<T extends NPSelection> = ReplaceKey<
person: Person, person: Person,
}; };
// TODO: recursive changing this down into the possesor etc. // TODO: recursive changing this down into the possesor etc.
export type EPSelectionState = {
subject: NPSelection | undefined,
predicate: {
type: "NP" | "Complement",
NP: NPSelection | undefined,
Complement: EqCompSelection | undefined,
},
equative: EquativeSelection,
};
export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"> & {
subject: NPSelection,
predicate: {
type: "NP",
selection: NPSelection,
} | {
type: "Complement",
selection: EqCompSelection,
},
};
export type EqCompType = "adjective" | "loc. adv."; // TODO: - more
export type EqCompSelection = AdjectiveSelection | LocativeAdverbSelection; // TODO: - more
export type EquativeSelection = {
tense: EquativeTense,
negative: boolean,
};
export type EquativeRendered = EquativeSelection & {
ps: SingleOrLengthOpts<PsString[]>,
person: Person,
hasBa: boolean,
}
export type EPRendered = {
type: "EPRendered",
king: "subject" | "predicate",
subject: Rendered<NPSelection>,
predicate: Rendered<NPSelection | EqCompSelection>,
equative: EquativeRendered,
englishBase?: string[],
}
export type EntryFeeder = {
nouns: EntryLookupPortal<NounEntry>,
verbs: EntryLookupPortal<VerbEntry>,
adjectives: EntryLookupPortal<AdjectiveEntry>,
locativeAdverbs: EntryLookupPortal<LocativeAdverbEntry>,
} | {
nouns: NounEntry[],
verbs: VerbEntry[],
adjectives: AdjectiveEntry[],
locativeAdverbs: LocativeAdverbEntry[],
}
export type EntryFeederSingleType<X extends VerbEntry | DictionaryEntry> = X[] | EntryLookupPortal<X>;
export type EntryLookupPortal<X extends VerbEntry | DictionaryEntry> = {
search: (s: string) => X[],
getByTs: (s: number) => X,
}