brought in new verb explorer and actually fixed roots and stems crash problem
This commit is contained in:
parent
759df4811e
commit
f306488e0d
|
@ -5,7 +5,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||||
"@lingdocs/lingdocs-main": "^0.2.0",
|
"@lingdocs/lingdocs-main": "^0.2.0",
|
||||||
"@lingdocs/pashto-inflector": "^1.7.0",
|
"@lingdocs/pashto-inflector": "^1.8.0",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
import {
|
|
||||||
Types as T,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import Select from "react-select";
|
|
||||||
import AsyncSelect from "react-select/async";
|
|
||||||
import {
|
|
||||||
makeSelectOption,
|
|
||||||
makeVerbSelectOption,
|
|
||||||
zIndexProps,
|
|
||||||
} from "./np-picker/picker-tools";
|
|
||||||
|
|
||||||
function EntrySelect<E extends T.DictionaryEntry | VerbEntry>(props: ({
|
|
||||||
entries: E[]
|
|
||||||
} | {
|
|
||||||
searchF: (search: string) => E[],
|
|
||||||
getByTs: (ts: number) => E | undefined,
|
|
||||||
}) & {
|
|
||||||
value: E | undefined,
|
|
||||||
onChange: (value: E | undefined) => void,
|
|
||||||
name: string | undefined,
|
|
||||||
isVerbSelect?: boolean,
|
|
||||||
opts: T.TextOptions,
|
|
||||||
}) {
|
|
||||||
function makeOption(e: E | T.DictionaryEntry) {
|
|
||||||
if ("entry" in e) {
|
|
||||||
return (props.isVerbSelect ? makeVerbSelectOption : makeSelectOption)(e, props.opts);
|
|
||||||
}
|
|
||||||
return makeSelectOption(e, props.opts);
|
|
||||||
}
|
|
||||||
const value = props.value ? makeOption(props.value) : undefined;
|
|
||||||
if ("searchF" in props) {
|
|
||||||
const options = (searchString: string) =>
|
|
||||||
new Promise<{ value: string, label: string | JSX.Element }[]>(resolve => {
|
|
||||||
resolve(props.searchF(searchString).map(makeOption));
|
|
||||||
});
|
|
||||||
const onChange = (v: { label: string | JSX.Element, value: string } | null) => {
|
|
||||||
if (!v) {
|
|
||||||
props.onChange(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const s = props.getByTs(parseInt(v.value));
|
|
||||||
if (!s) return;
|
|
||||||
props.onChange(s);
|
|
||||||
}
|
|
||||||
return <div>
|
|
||||||
<AsyncSelect
|
|
||||||
isSearchable={true}
|
|
||||||
className="mb-2"
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
defaultOptions={[]}
|
|
||||||
loadOptions={options}
|
|
||||||
placeholder={props.name ? `Select ${props.name}...` : undefined}
|
|
||||||
{...zIndexProps}
|
|
||||||
/>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
const options = props.entries
|
|
||||||
.sort((a, b) => {
|
|
||||||
if ("entry" in a) {
|
|
||||||
return a.entry.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(makeOption);
|
|
||||||
const onChange = (v: { label: string | JSX.Element, value: string } | null) => {
|
|
||||||
if (!v) {
|
|
||||||
props.onChange(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const s = props.entries.find(e => (
|
|
||||||
("entry" in e)
|
|
||||||
? e.entry.ts.toString() === v.value
|
|
||||||
: e.ts.toString() === v.value
|
|
||||||
));
|
|
||||||
if (!s) return;
|
|
||||||
props.onChange(s);
|
|
||||||
}
|
|
||||||
return <div>
|
|
||||||
<Select
|
|
||||||
isSearchable={true}
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
className="mb-2"
|
|
||||||
options={options}
|
|
||||||
placeholder={props.name ? `Select ${props.name}...` : undefined}
|
|
||||||
{...zIndexProps}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EntrySelect;
|
|
|
@ -6,9 +6,10 @@ import {
|
||||||
inflectWord,
|
inflectWord,
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
getEnglishWord,
|
getEnglishWord,
|
||||||
|
Types as T,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
|
||||||
function InflectionCarousel({ items }: { items: (NounEntry | AdjectiveEntry)[] }) {
|
function InflectionCarousel({ items }: { items: (T.NounEntry | T.AdjectiveEntry)[] }) {
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
return "no items for carousel";
|
return "no items for carousel";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
// TODO: DEPRECATE THIS AND USE THE PASHTO INFLECTER NP PRONOUN PICKER!
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Types as T,
|
Types as T,
|
||||||
ButtonSelect,
|
ButtonSelect,
|
||||||
|
useStickyState,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import useStickyState from "../../useStickyState";
|
|
||||||
|
|
||||||
const gColors = {
|
const gColors = {
|
||||||
masc: "LightSkyBlue",
|
masc: "LightSkyBlue",
|
||||||
|
@ -143,4 +145,3 @@ function PronounPicker({ onChange, pronoun, isObject }: {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PronounPicker;
|
export default PronounPicker;
|
||||||
|
|
|
@ -1,180 +0,0 @@
|
||||||
import Select from "react-select";
|
|
||||||
import {
|
|
||||||
zIndexProps,
|
|
||||||
} from "./np-picker/picker-tools";
|
|
||||||
import {
|
|
||||||
ButtonSelect,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import { isPerfectTense } from "../lib/phrase-building/vp-tools";
|
|
||||||
|
|
||||||
const tenseOptions: { label: string | JSX.Element, value: VerbTense }[] = [{
|
|
||||||
label: <div><i className="fas fa-video mr-2" />present</div>,
|
|
||||||
value: "presentVerb",
|
|
||||||
}, {
|
|
||||||
label: <div><i className="fas fa-camera mr-2" />subjunctive</div>,
|
|
||||||
value: "subjunctiveVerb",
|
|
||||||
}, {
|
|
||||||
label: <div><i className="fas fa-video mr-2" />imperfective future</div>,
|
|
||||||
value: "imperfectiveFuture",
|
|
||||||
}, {
|
|
||||||
label: <div><i className="fas fa-camera mr-2" />perfective future</div>,
|
|
||||||
value: "perfectiveFuture",
|
|
||||||
}, {
|
|
||||||
label: <div><i className="fas fa-video mr-2" />continuous past</div>,
|
|
||||||
value: "imperfectivePast",
|
|
||||||
}, {
|
|
||||||
label: <div><i className="fas fa-camera mr-2" />simple past</div>,
|
|
||||||
value: "perfectivePast",
|
|
||||||
}, {
|
|
||||||
label: <div><i className="fas fa-video mr-2" />habitual continual past</div>,
|
|
||||||
value: "habitualImperfectivePast",
|
|
||||||
}, {
|
|
||||||
label: <div><i className="fas fa-camera mr-2" />habitual simple past</div>,
|
|
||||||
value: "habitualPerfectivePast",
|
|
||||||
}];
|
|
||||||
|
|
||||||
const perfectTenseOptions: { label: string | JSX.Element, value: PerfectTense }[] = [{
|
|
||||||
label: "Present Perfect",
|
|
||||||
value: "present perfect",
|
|
||||||
}, {
|
|
||||||
label: "Habitual Perfect",
|
|
||||||
value: "habitual perfect",
|
|
||||||
}, {
|
|
||||||
label: "Subjunctive Perfect",
|
|
||||||
value: "subjunctive perfect",
|
|
||||||
}, {
|
|
||||||
label: "Future Perfect",
|
|
||||||
value: "future perfect",
|
|
||||||
}, {
|
|
||||||
label: "Past Perfect",
|
|
||||||
value: "past perfect",
|
|
||||||
}, {
|
|
||||||
label: `"Would Be" Perfect`,
|
|
||||||
value: "wouldBe perfect",
|
|
||||||
}, {
|
|
||||||
label: "Past Subjunctive Perfect",
|
|
||||||
value: "pastSubjunctive perfect",
|
|
||||||
}];
|
|
||||||
|
|
||||||
function TensePicker({ onChange, verb, mode }: {
|
|
||||||
verbs: VerbEntry[],
|
|
||||||
verb: VerbSelection | undefined,
|
|
||||||
onChange: (p: VerbSelection | undefined) => void,
|
|
||||||
mode: "charts" | "phrases",
|
|
||||||
}) {
|
|
||||||
function onTenseSelect(o: { value: VerbTense | PerfectTense } | null) {
|
|
||||||
const value = o?.value ? o.value : undefined;
|
|
||||||
if (verb && value) {
|
|
||||||
if (isPerfectTense(value)) {
|
|
||||||
onChange({
|
|
||||||
...verb,
|
|
||||||
tense: value,
|
|
||||||
tenseCategory: "perfect",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
onChange({
|
|
||||||
...verb,
|
|
||||||
tense: value,
|
|
||||||
tenseCategory: verb.tenseCategory === "perfect" ? "basic" : verb.tenseCategory,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function moveTense(dir: "forward" | "back") {
|
|
||||||
if (!verb) return;
|
|
||||||
return () => {
|
|
||||||
const tenses = verb.tenseCategory === "perfect" ? perfectTenseOptions : tenseOptions;
|
|
||||||
const currIndex = tenses.findIndex(tn => tn.value === verb.tense)
|
|
||||||
if (currIndex === -1) {
|
|
||||||
console.error("error moving tense", dir);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newIndex = dir === "forward"
|
|
||||||
? ((currIndex + 1) % tenses.length)
|
|
||||||
: (currIndex === 0 ? (tenses.length - 1) : (currIndex - 1))
|
|
||||||
const newTense = tenses[newIndex];
|
|
||||||
onTenseSelect(newTense);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function onPosNegSelect(value: string) {
|
|
||||||
if (verb) {
|
|
||||||
onChange({
|
|
||||||
...verb,
|
|
||||||
negative: value === "true",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function onTenseCategorySelect(value: "basic" | "modal" | "perfect") {
|
|
||||||
if (verb) {
|
|
||||||
if (value === "perfect") {
|
|
||||||
onChange({
|
|
||||||
...verb,
|
|
||||||
tenseCategory: value,
|
|
||||||
tense: isPerfectTense(verb.tense) ? verb.tense : "present perfect",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
onChange({
|
|
||||||
...verb,
|
|
||||||
tenseCategory: value,
|
|
||||||
tense: isPerfectTense(verb.tense) ? "presentVerb" : verb.tense,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const tOptions = (verb?.tenseCategory === "perfect") ? perfectTenseOptions : tenseOptions;
|
|
||||||
return <div>
|
|
||||||
<div style={{ maxWidth: "300px", minWidth: "250px", margin: "0 auto" }}>
|
|
||||||
<div className="d-flex flex-row justify-content-between align-items-center">
|
|
||||||
<div className="h5">Tense:</div>
|
|
||||||
{verb && <div className="mb-2">
|
|
||||||
<ButtonSelect
|
|
||||||
small
|
|
||||||
value={verb.tenseCategory}
|
|
||||||
options={[{
|
|
||||||
label: "Basic",
|
|
||||||
value: "basic",
|
|
||||||
}, {
|
|
||||||
label: "Perfect",
|
|
||||||
value: "perfect",
|
|
||||||
}, {
|
|
||||||
label: "Modal",
|
|
||||||
value: "modal",
|
|
||||||
}]}
|
|
||||||
handleChange={onTenseCategorySelect}
|
|
||||||
/>
|
|
||||||
</div>}
|
|
||||||
</div>
|
|
||||||
<Select
|
|
||||||
isSearchable={false}
|
|
||||||
// for some reason can't use tOptions with find here;
|
|
||||||
value={verb && ([...tenseOptions, ...perfectTenseOptions].find(o => o.value === verb.tense))}
|
|
||||||
onChange={onTenseSelect}
|
|
||||||
className="mb-2"
|
|
||||||
options={tOptions}
|
|
||||||
{...zIndexProps}
|
|
||||||
/>
|
|
||||||
{verb && <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>
|
|
||||||
{mode !== "charts" && <ButtonSelect
|
|
||||||
small
|
|
||||||
value={verb.negative.toString()}
|
|
||||||
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 TensePicker;
|
|
|
@ -1,148 +0,0 @@
|
||||||
import {
|
|
||||||
ButtonSelect,
|
|
||||||
Types as T,
|
|
||||||
RootsAndStems,
|
|
||||||
getVerbInfo,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import Hider from "@lingdocs/pashto-inflector/dist/components/Hider";
|
|
||||||
import {
|
|
||||||
makeVerbSelection,
|
|
||||||
} from "./phrase-builder/verb-selection";
|
|
||||||
import EntrySelect from "./EntrySelect";
|
|
||||||
import useStickyState from "../useStickyState";
|
|
||||||
|
|
||||||
// TODO: dark on past tense selecitons
|
|
||||||
|
|
||||||
function VerbPicker({ onChange, subject, changeSubject, verb, verbs, opts, verbLocked }: {
|
|
||||||
verbs: VerbEntry[],
|
|
||||||
verb: VerbSelection | undefined,
|
|
||||||
subject: NPSelection | undefined,
|
|
||||||
onChange: (p: VerbSelection | undefined) => void,
|
|
||||||
changeSubject: (p: NPSelection | undefined) => void,
|
|
||||||
opts: T.TextOptions,
|
|
||||||
verbLocked: boolean,
|
|
||||||
}) {
|
|
||||||
const [showRootsAndStems, setShowRootsAndStems] = useStickyState<boolean>(false, "showRootsAndStems");
|
|
||||||
const infoRaw = verb ? getVerbInfo(verb.verb.entry, verb.verb.complement) : undefined;
|
|
||||||
const info = (!infoRaw || !verb)
|
|
||||||
? undefined
|
|
||||||
: ("stative" in infoRaw)
|
|
||||||
? infoRaw[verb.isCompound === "stative" ? "stative" : "dynamic"]
|
|
||||||
: ("transitive" in infoRaw)
|
|
||||||
? infoRaw[verb.transitivity === "grammatically transitive" ? "grammaticallyTransitive" : "transitive"]
|
|
||||||
: infoRaw;
|
|
||||||
if (info && ("stative" in info || "transitive" in info)) {
|
|
||||||
return <div>ERROR: Verb version should be select first</div>;
|
|
||||||
}
|
|
||||||
// const [filters, useFilters] = useState<Filters>({
|
|
||||||
// stative: true,
|
|
||||||
// dynamic: true,
|
|
||||||
// transitive: true,
|
|
||||||
// intransitive: true,
|
|
||||||
// grammaticallyTransitive: true,
|
|
||||||
// });
|
|
||||||
function onVerbSelect(v: VerbEntry | undefined) {
|
|
||||||
// TODO: what to do when clearing
|
|
||||||
if (!v) {
|
|
||||||
return onChange(v);
|
|
||||||
}
|
|
||||||
onChange(makeVerbSelection(v, changeSubject, verb));
|
|
||||||
}
|
|
||||||
function onVoiceSelect(value: "active" | "passive") {
|
|
||||||
if (verb && verb.changeVoice) {
|
|
||||||
if (value === "passive" && (typeof verb.object === "object")) {
|
|
||||||
changeSubject(verb.object);
|
|
||||||
}
|
|
||||||
if (value === "active") {
|
|
||||||
changeSubject(undefined);
|
|
||||||
}
|
|
||||||
onChange(verb.changeVoice(value, value === "active" ? subject : undefined));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function notInstransitive(t: "transitive" | "intransitive" | "grammatically transitive"): "transitive" | "grammatically transitive" {
|
|
||||||
return t === "intransitive" ? "transitive" : t;
|
|
||||||
}
|
|
||||||
function handleChangeTransitivity(t: "transitive" | "grammatically transitive") {
|
|
||||||
if (verb && verb.changeTransitivity) {
|
|
||||||
onChange(verb.changeTransitivity(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function handleChangeStatDyn(c: "stative" | "dynamic") {
|
|
||||||
if (verb && verb.changeStatDyn) {
|
|
||||||
onChange(verb.changeStatDyn(c));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <div className="mb-3">
|
|
||||||
{!verbLocked && <div style={{ maxWidth: "300px", margin: "0 auto" }}>
|
|
||||||
<div className="h5">Verb:</div>
|
|
||||||
<EntrySelect
|
|
||||||
entries={verbs}
|
|
||||||
value={verb?.verb}
|
|
||||||
onChange={onVerbSelect}
|
|
||||||
name="Verb"
|
|
||||||
isVerbSelect
|
|
||||||
opts={opts}
|
|
||||||
/>
|
|
||||||
</div>}
|
|
||||||
{info && <div className="mt-3 mb-1 text-center">
|
|
||||||
<Hider
|
|
||||||
showing={showRootsAndStems}
|
|
||||||
label="🌳 Roots and Stems"
|
|
||||||
handleChange={() => setShowRootsAndStems(p => !p)}
|
|
||||||
hLevel={5}
|
|
||||||
>
|
|
||||||
<RootsAndStems
|
|
||||||
textOptions={opts}
|
|
||||||
info={info}
|
|
||||||
/>
|
|
||||||
</Hider>
|
|
||||||
</div>}
|
|
||||||
<div className="d-flex flex-row justify-content-around flex-wrap" style={{ maxWidth: "400px", margin: "0 auto" }}>
|
|
||||||
{verb && verb.changeTransitivity && <div className="text-center my-2">
|
|
||||||
<ButtonSelect
|
|
||||||
small
|
|
||||||
options={[{
|
|
||||||
label: "gramm. trans.",
|
|
||||||
value: "grammatically transitive",
|
|
||||||
}, {
|
|
||||||
label: "trans.",
|
|
||||||
value: "transitive",
|
|
||||||
}]}
|
|
||||||
value={notInstransitive(verb.transitivity)}
|
|
||||||
handleChange={handleChangeTransitivity}
|
|
||||||
/>
|
|
||||||
</div>}
|
|
||||||
{verb && verb.changeVoice && <div className="text-center my-2">
|
|
||||||
<ButtonSelect
|
|
||||||
small
|
|
||||||
value={verb.voice}
|
|
||||||
options={[{
|
|
||||||
label: "Active",
|
|
||||||
value: "active",
|
|
||||||
}, {
|
|
||||||
label: "Passive",
|
|
||||||
value: "passive",
|
|
||||||
}]}
|
|
||||||
handleChange={onVoiceSelect}
|
|
||||||
/>
|
|
||||||
</div>}
|
|
||||||
{verb && verb.changeStatDyn && <div className="text-center my-2">
|
|
||||||
<ButtonSelect
|
|
||||||
small
|
|
||||||
options={[{
|
|
||||||
label: "stative",
|
|
||||||
value: "stative",
|
|
||||||
}, {
|
|
||||||
label: "dynamic",
|
|
||||||
value: "dynamic",
|
|
||||||
}]}
|
|
||||||
value={verb.isCompound ? verb.isCompound : "stative"}
|
|
||||||
handleChange={handleChangeStatDyn}
|
|
||||||
/>
|
|
||||||
</div>}
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default VerbPicker;
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
Types as T,
|
Types as T,
|
||||||
personGender,
|
personGender,
|
||||||
personIsPlural,
|
personIsPlural,
|
||||||
|
typePredicates,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import {
|
import {
|
||||||
ExplorerState,
|
ExplorerState,
|
||||||
|
@ -14,13 +15,13 @@ import {
|
||||||
equativeMachine,
|
equativeMachine,
|
||||||
assembleEquativeOutput,
|
assembleEquativeOutput,
|
||||||
} from "../../lib/equative-machine";
|
} from "../../lib/equative-machine";
|
||||||
import {
|
const {
|
||||||
isUnisexNounEntry,
|
isUnisexNounEntry,
|
||||||
isAdjectiveEntry,
|
isAdjectiveEntry,
|
||||||
isVerbEntry,
|
isVerbEntry,
|
||||||
isLocativeAdverbEntry,
|
isLocativeAdverbEntry,
|
||||||
isNounEntry,
|
isNounEntry,
|
||||||
} from "../../lib/type-predicates";
|
} = typePredicates;
|
||||||
|
|
||||||
function chooseLength<O>(o: T.SingleOrLengthOpts<O>, length: "short" | "long"): O {
|
function chooseLength<O>(o: T.SingleOrLengthOpts<O>, length: "short" | "long"): O {
|
||||||
return ("long" in o) ? o[length] : o;
|
return ("long" in o) ? o[length] : o;
|
||||||
|
@ -54,14 +55,14 @@ function SingleItemDisplay({ state }: { state: ExplorerState }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeComplement(entry: AdjectiveEntry | LocativeAdverbEntry): Compliment {
|
function makeComplement(entry: T.AdjectiveEntry | T.LocativeAdverbEntry): Compliment {
|
||||||
return {
|
return {
|
||||||
type: "compliment",
|
type: "compliment",
|
||||||
entry,
|
entry,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeNounPhrase(entry: NounEntry | UnisexNounEntry | VerbEntry, state: ExplorerState, entity: "subject" | "predicate"): NounPhrase {
|
function makeNounPhrase(entry: T.NounEntry | T.UnisexNounEntry | T.VerbEntry, state: ExplorerState, entity: "subject" | "predicate"): NounPhrase {
|
||||||
if (isVerbEntry(entry)) {
|
if (isVerbEntry(entry)) {
|
||||||
return {
|
return {
|
||||||
type: "participle",
|
type: "participle",
|
||||||
|
@ -76,7 +77,7 @@ function makeNounPhrase(entry: NounEntry | UnisexNounEntry | VerbEntry, state: E
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeBlockWPronouns(e: AdjectiveEntry | UnisexNounEntry | LocativeAdverbEntry, tense: EquativeTense, negative: boolean, length?: "short" | "long"): T.SingleOrLengthOpts<T.VerbBlock> {
|
export function makeBlockWPronouns(e: T.AdjectiveEntry | T.UnisexNounEntry | T.LocativeAdverbEntry, tense: EquativeTense, negative: boolean, length?: "short" | "long"): T.SingleOrLengthOpts<T.VerbBlock> {
|
||||||
// if the output's gonna have long / short forms (if it's past or wouldBe) then recursive call to make the long and short versions
|
// if the output's gonna have long / short forms (if it's past or wouldBe) then recursive call to make the long and short versions
|
||||||
if (!length && "long" in assembleEquativeOutput(equativeMachine({
|
if (!length && "long" in assembleEquativeOutput(equativeMachine({
|
||||||
subject: { type: "pronoun", pronounType: "near", person: 0 },
|
subject: { type: "pronoun", pronounType: "near", person: 0 },
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { nouns, adjectives, verbs, adverbs } from "../../words/words";
|
import { nouns, adjectives, verbs, adverbs } from "../../words/words";
|
||||||
import {
|
import {
|
||||||
isLocativeAdverbEntry,
|
Types as T,
|
||||||
isUnisexNounEntry,
|
typePredicates as tp,
|
||||||
} from "../../lib/type-predicates";
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
|
||||||
function sort<T extends (AdjectiveEntry | NounEntry | VerbEntry | AdverbEntry)>(arr: Readonly<T[]>): T[] {
|
function sort<T extends (T.AdjectiveEntry | T.NounEntry | T.VerbEntry | T.AdverbEntry)>(arr: Readonly<T[]>): T[] {
|
||||||
if ("entry" in arr[0]) {
|
if ("entry" in arr[0]) {
|
||||||
return [...arr].sort((a, b) => (
|
return [...arr].sort((a, b) => (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -17,15 +17,15 @@ function sort<T extends (AdjectiveEntry | NounEntry | VerbEntry | AdverbEntry)>(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
const unisexNouns = sort(nouns.filter(x => isUnisexNounEntry(x)) as UnisexNounEntry[]);
|
const unisexNouns = sort(nouns.filter(x => tp.isUnisexNounEntry(x)) as T.UnisexNounEntry[]);
|
||||||
const nonUnisexNouns = sort(nouns.filter(x => !isUnisexNounEntry(x)) as (MascNounEntry | FemNounEntry)[]);
|
const nonUnisexNouns = sort(nouns.filter(x => !tp.isUnisexNounEntry(x)) as (T.MascNounEntry | T.FemNounEntry)[]);
|
||||||
|
|
||||||
const inputs = {
|
const inputs = {
|
||||||
adjective: sort(adjectives),
|
adjective: sort(adjectives),
|
||||||
unisexNoun: unisexNouns,
|
unisexNoun: unisexNouns,
|
||||||
noun: nonUnisexNouns,
|
noun: nonUnisexNouns,
|
||||||
participle: sort(verbs),
|
participle: sort(verbs),
|
||||||
adverb: sort(adverbs.filter(isLocativeAdverbEntry)),
|
adverb: sort(adverbs.filter(tp.isLocativeAdverbEntry)),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultAdjective = inputs.adjective.find(ps => ps.p === "زوړ") || inputs.adjective[0];
|
export const defaultAdjective = inputs.adjective.find(ps => ps.p === "زوړ") || inputs.adjective[0];
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import inputs from "./explorer-inputs";
|
import inputs from "./explorer-inputs";
|
||||||
import { ExplorerState, ExplorerReducerAction } from "./explorer-types";
|
import { ExplorerState, ExplorerReducerAction } from "./explorer-types";
|
||||||
|
import {
|
||||||
|
Types as T,
|
||||||
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
|
||||||
export function reducer(state: ExplorerState, action: ExplorerReducerAction): ExplorerState {
|
export function reducer(state: ExplorerState, action: ExplorerReducerAction): ExplorerState {
|
||||||
if (action.type === "setPredicate") {
|
if (action.type === "setPredicate") {
|
||||||
const pile = inputs[state.predicate.type] as (UnisexNounEntry | AdjectiveEntry)[];
|
const pile = inputs[state.predicate.type] as (T.UnisexNounEntry | T.AdjectiveEntry)[];
|
||||||
const predicate = (pile.find(p => p.ts === action.payload) || pile[0]);
|
const predicate = (pile.find(p => p.ts === action.payload) || pile[0]);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -14,9 +14,17 @@ import {
|
||||||
getEnglishWord,
|
getEnglishWord,
|
||||||
Types as T,
|
Types as T,
|
||||||
removeFVarients,
|
removeFVarients,
|
||||||
|
typePredicates,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import { isAdjectiveEntry, isAdverbEntry, isFemNounEntry, isMascNounEntry, isNounEntry, isPluralNounEntry } from "../../lib/type-predicates";
|
|
||||||
import Select from "react-select";
|
import Select from "react-select";
|
||||||
|
const {
|
||||||
|
isAdjectiveEntry,
|
||||||
|
isAdverbEntry,
|
||||||
|
isFemNounEntry,
|
||||||
|
isMascNounEntry,
|
||||||
|
isNounEntry,
|
||||||
|
isPluralNounEntry,
|
||||||
|
} = typePredicates;
|
||||||
|
|
||||||
const zIndexProps = {
|
const zIndexProps = {
|
||||||
menuPortalTarget: document.body,
|
menuPortalTarget: document.body,
|
||||||
|
@ -83,7 +91,7 @@ export function InputSelector({ state, dispatch, entity }: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = state[entity].type;
|
const type = state[entity].type;
|
||||||
const entry: NounEntry | VerbEntry | AdjectiveEntry | LocativeAdverbEntry | undefined = type === "pronouns"
|
const entry: T.NounEntry | T.VerbEntry | T.AdjectiveEntry | T.LocativeAdverbEntry | undefined = type === "pronouns"
|
||||||
? undefined
|
? undefined
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
: state[entity][type];
|
: state[entity][type];
|
||||||
|
@ -164,7 +172,7 @@ function GenderAndNumberSelect({ state, dispatch, entity }: {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeOption(e: VerbEntry | NounEntry | AdjectiveEntry | LocativeAdverbEntry): { value: string, label: string } {
|
function makeOption(e: T.VerbEntry | T.NounEntry | T.AdjectiveEntry | T.LocativeAdverbEntry): { value: string, label: string } {
|
||||||
const entry = "entry" in e ? e.entry : e;
|
const entry = "entry" in e ? e.entry : e;
|
||||||
// TODO: THIS IS SUUUPER SKETCH
|
// TODO: THIS IS SUUUPER SKETCH
|
||||||
const eng = (isNounEntry(e) || isAdjectiveEntry(e) || isAdverbEntry(e))
|
const eng = (isNounEntry(e) || isAdjectiveEntry(e) || isAdverbEntry(e))
|
||||||
|
|
|
@ -17,14 +17,14 @@ export type SubjectEntityInfo = EntitiyInfo & { type: SubjectType };
|
||||||
|
|
||||||
export type PredicateEntityInfo = EntitiyInfo & {
|
export type PredicateEntityInfo = EntitiyInfo & {
|
||||||
type: PredicateType,
|
type: PredicateType,
|
||||||
adjective: AdjectiveEntry,
|
adjective: import("@lingdocs/pashto-inflector").Types.AdjectiveEntry,
|
||||||
adverb: LocativeAdverbEntry,
|
adverb: import("@lingdocs/pashto-inflector").Types.LocativeAdverbEntry,
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntitiyInfo = {
|
type EntitiyInfo = {
|
||||||
noun: NounEntry,
|
noun: import("@lingdocs/pashto-inflector").Types.NounEntry,
|
||||||
participle: VerbEntry,
|
participle: import("@lingdocs/pashto-inflector").Types.VerbEntry,
|
||||||
unisexNoun: UnisexNounEntry,
|
unisexNoun: import("@lingdocs/pashto-inflector").Types.UnisexNounEntry,
|
||||||
info: {
|
info: {
|
||||||
number: NounNumber,
|
number: NounNumber,
|
||||||
gender: T.Gender,
|
gender: T.Gender,
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
import {
|
|
||||||
makeNounSelection,
|
|
||||||
} from "./picker-tools";
|
|
||||||
import {
|
|
||||||
ButtonSelect,
|
|
||||||
InlinePs,
|
|
||||||
Types as T,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
// import { useState } from "react";
|
|
||||||
// import { isFemNounEntry, isPattern1Entry, isPattern2Entry, isPattern3Entry, isPattern4Entry, isPattern5Entry, isPattern6FemEntry } from "../../lib/type-predicates";
|
|
||||||
import EntrySelect from "../EntrySelect";
|
|
||||||
|
|
||||||
// const filterOptions = [
|
|
||||||
// {
|
|
||||||
// label: "1",
|
|
||||||
// value: "1",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// label: "2",
|
|
||||||
// value: "2",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// label: "3",
|
|
||||||
// value: "3",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// label: "4",
|
|
||||||
// value: "4",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// label: "5",
|
|
||||||
// value: "5",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// label: "6",
|
|
||||||
// value: "6",
|
|
||||||
// },
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// type FilterPattern = "1" | "2" | "3" | "4" | "5" | "6";
|
|
||||||
|
|
||||||
// function nounFilter(p: FilterPattern | undefined) {
|
|
||||||
// return p === undefined
|
|
||||||
// ? () => true
|
|
||||||
// : (p === "1")
|
|
||||||
// ? isPattern1Entry
|
|
||||||
// : (p === "2")
|
|
||||||
// ? isPattern2Entry
|
|
||||||
// : (p === "3")
|
|
||||||
// ? isPattern3Entry
|
|
||||||
// : (p === "4")
|
|
||||||
// ? isPattern4Entry
|
|
||||||
// : (p === "5")
|
|
||||||
// ? isPattern5Entry
|
|
||||||
// : (p === "6")
|
|
||||||
// ? (n: NounEntry) => (isFemNounEntry(n) && isPattern6FemEntry(n))
|
|
||||||
// : () => true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
function NPNounPicker(props: ({
|
|
||||||
nouns: NounEntry[],
|
|
||||||
} | {
|
|
||||||
nouns: (s: string) => NounEntry[],
|
|
||||||
getNounByTs: (ts: number) => NounEntry | undefined;
|
|
||||||
}) & {
|
|
||||||
noun: NounSelection | undefined,
|
|
||||||
onChange: (p: NounSelection | undefined) => void,
|
|
||||||
clearButton?: JSX.Element,
|
|
||||||
opts: T.TextOptions,
|
|
||||||
}) {
|
|
||||||
// const [patternFilter, setPatternFilter] = useState<FilterPattern | undefined>(undefined);
|
|
||||||
// const [showFilter, setShowFilter] = useState<boolean>(false)
|
|
||||||
// const nounsFiltered = props.nouns
|
|
||||||
// .filter(nounFilter(patternFilter))
|
|
||||||
// .sort((a, b) => (a.p.localeCompare(b.p, "af-PS")));
|
|
||||||
function onEntrySelect(entry: NounEntry | undefined) {
|
|
||||||
if (!entry) {
|
|
||||||
return props.onChange(undefined);
|
|
||||||
}
|
|
||||||
props.onChange(makeNounSelection(entry));
|
|
||||||
}
|
|
||||||
// function handleFilterClose() {
|
|
||||||
// setPatternFilter(undefined);
|
|
||||||
// setShowFilter(false);
|
|
||||||
// }
|
|
||||||
return <div style={{ maxWidth: "225px", minWidth: "125px" }}>
|
|
||||||
<div className="d-flex flex-row justify-content-left">
|
|
||||||
{props.clearButton}
|
|
||||||
{/* {(!showFilter && !(noun?.dynamicComplement)) && <div className="text-right">
|
|
||||||
<button className="btn btn-sm btn-light mb-2 text-small" onClick={() => setShowFilter(true)}>
|
|
||||||
<i className="fas fa-filter fa-xs" />
|
|
||||||
</button>
|
|
||||||
</div>} */}
|
|
||||||
</div>
|
|
||||||
{/* {showFilter && <div className="mb-2 text-center">
|
|
||||||
<div className="d-flex flex-row justify-content-between">
|
|
||||||
<div className="text-small mb-1">Filter by inflection pattern</div>
|
|
||||||
<div className="clickable" onClick={handleFilterClose}>X</div>
|
|
||||||
</div>
|
|
||||||
<ButtonSelect
|
|
||||||
options={filterOptions}
|
|
||||||
// @ts-ignore
|
|
||||||
value={patternFilter}
|
|
||||||
// @ts-ignore
|
|
||||||
handleChange={setPatternFilter}
|
|
||||||
/>
|
|
||||||
</div>} */}
|
|
||||||
{!(props.noun && props.noun.dynamicComplement) ? <div>
|
|
||||||
<EntrySelect
|
|
||||||
value={props.noun?.entry}
|
|
||||||
{..."getNounByTs" in props ? {
|
|
||||||
getByTs: props.getNounByTs,
|
|
||||||
searchF: props.nouns
|
|
||||||
} : {
|
|
||||||
entries: props.nouns,
|
|
||||||
}}
|
|
||||||
onChange={onEntrySelect}
|
|
||||||
name="Noun"
|
|
||||||
opts={props.opts}
|
|
||||||
/>
|
|
||||||
</div> : <div>
|
|
||||||
{props.noun && <div>
|
|
||||||
<div className="mb-2">Included in Dyn. Compound:</div>
|
|
||||||
<div className="mb-3 text-center">
|
|
||||||
<InlinePs opts={props.opts}>
|
|
||||||
{{ p: props.noun.entry.p, f: props.noun.entry.f }}
|
|
||||||
</InlinePs>
|
|
||||||
<div className="text-muted">{props.noun.entry.e}</div>
|
|
||||||
</div>
|
|
||||||
</div>}
|
|
||||||
</div>}
|
|
||||||
{props.noun && <div className="my-2 d-flex flex-row justify-content-around align-items-center">
|
|
||||||
<div>
|
|
||||||
{props.noun.changeGender ? <ButtonSelect
|
|
||||||
small
|
|
||||||
options={[
|
|
||||||
{ label: "Masc", value: "masc" },
|
|
||||||
{ label: "Fem", value: "fem" },
|
|
||||||
]}
|
|
||||||
value={props.noun.gender}
|
|
||||||
handleChange={(g) => {
|
|
||||||
if (!props.noun || !props.noun.changeGender) return;
|
|
||||||
props.onChange(props.noun.changeGender(g));
|
|
||||||
}}
|
|
||||||
/> : props.noun.gender === "masc" ? "Masc." : "Fem."}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{props.noun.changeNumber ? <ButtonSelect
|
|
||||||
small
|
|
||||||
options={[
|
|
||||||
{ label: "Sing.", value: "singular" },
|
|
||||||
{ label: "Plur.", value: "plural" },
|
|
||||||
]}
|
|
||||||
value={props.noun.number}
|
|
||||||
handleChange={(n) => {
|
|
||||||
if (!props.noun || !props.noun.changeNumber) return;
|
|
||||||
props.onChange(props.noun.changeNumber(n));
|
|
||||||
}}
|
|
||||||
/> : props.noun.number === "singular" ? "Sing." : "Plur."}
|
|
||||||
</div>
|
|
||||||
</div>}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NPNounPicker;
|
|
|
@ -1,52 +0,0 @@
|
||||||
import EntrySelect from "../EntrySelect";
|
|
||||||
import {
|
|
||||||
Types as T,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
|
|
||||||
function makeParticipleSelection(verb: VerbEntry): ParticipleSelection {
|
|
||||||
return {
|
|
||||||
type: "participle",
|
|
||||||
verb,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function NPParticiplePicker(props: ({
|
|
||||||
verbs: VerbEntry[],
|
|
||||||
} | {
|
|
||||||
verbs: (s: string) => VerbEntry[],
|
|
||||||
getVerbByTs: (ts: number) => VerbEntry | undefined;
|
|
||||||
}) & {
|
|
||||||
participle: ParticipleSelection | undefined,
|
|
||||||
onChange: (p: ParticipleSelection | undefined) => void,
|
|
||||||
clearButton: JSX.Element,
|
|
||||||
opts: T.TextOptions,
|
|
||||||
}) {
|
|
||||||
function onEntrySelect(entry: VerbEntry | undefined) {
|
|
||||||
if (!entry) {
|
|
||||||
props.onChange(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
props.onChange(makeParticipleSelection(entry));
|
|
||||||
}
|
|
||||||
return <div style={{ maxWidth: "225px" }}>
|
|
||||||
{props.clearButton}
|
|
||||||
<EntrySelect
|
|
||||||
value={props.participle?.verb}
|
|
||||||
{..."getVerbByTs" in props ? {
|
|
||||||
getByTs: props.getVerbByTs,
|
|
||||||
searchF: props.verbs,
|
|
||||||
} : {
|
|
||||||
entries: props.verbs,
|
|
||||||
}}
|
|
||||||
onChange={onEntrySelect}
|
|
||||||
name="Pariticple"
|
|
||||||
opts={props.opts}
|
|
||||||
/>
|
|
||||||
{props.participle && <div className="my-2 d-flex flex-row justify-content-around align-items-center">
|
|
||||||
<div>Masc.</div>
|
|
||||||
<div>Plur.</div>
|
|
||||||
</div>}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NPParticiplePicker;
|
|
|
@ -1,113 +0,0 @@
|
||||||
import PronounPicker from "./NPPronounPicker";
|
|
||||||
import NounPicker from "./NPNounPicker";
|
|
||||||
import ParticiplePicker from "./NPParticiplePicker";
|
|
||||||
// import { getEnglishPronoun } from "../../lib/english-pronoun-tools";
|
|
||||||
// import { ButtonSelect } from "@lingdocs/pashto-inflector";
|
|
||||||
import { randomPerson } from "../../lib/np-tools";
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import {
|
|
||||||
Types as T,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
// import { capitalizeFirstLetter } from "../../lib/text-tools";
|
|
||||||
|
|
||||||
const npTypes: NPType[] = ["pronoun", "noun", "participle"];
|
|
||||||
|
|
||||||
function NPPicker(props: {
|
|
||||||
onChange: (nps: NPSelection | undefined) => void,
|
|
||||||
np: NPSelection | undefined,
|
|
||||||
counterPart: NPSelection | VerbObject | undefined,
|
|
||||||
asObject?: boolean,
|
|
||||||
opts: T.TextOptions,
|
|
||||||
} & ({
|
|
||||||
nouns: (s: string) => NounEntry[],
|
|
||||||
verbs: (s: string) => VerbEntry[],
|
|
||||||
getNounByTs: (ts: number) => NounEntry | undefined,
|
|
||||||
getVerbByTs: (ts: number) => VerbEntry | undefined,
|
|
||||||
} | {
|
|
||||||
nouns: NounEntry[],
|
|
||||||
verbs: VerbEntry[],
|
|
||||||
})) {
|
|
||||||
const [npType, setNpType] = useState<NPType | undefined>(props.np ? props.np.type : undefined);
|
|
||||||
useEffect(() => {
|
|
||||||
setNpType(props.np ? props.np.type : undefined);
|
|
||||||
}, [props.np]);
|
|
||||||
function handleClear() {
|
|
||||||
if (props.np && props.np.type === "noun" && props.np.dynamicComplement) return;
|
|
||||||
setNpType(undefined);
|
|
||||||
props.onChange(undefined);
|
|
||||||
}
|
|
||||||
function handleNPTypeChange(ntp: NPType) {
|
|
||||||
if (ntp === "pronoun") {
|
|
||||||
const person = randomPerson({ counterPart: props.counterPart });
|
|
||||||
const pronoun: PronounSelection = {
|
|
||||||
type: "pronoun",
|
|
||||||
person,
|
|
||||||
distance: "far",
|
|
||||||
};
|
|
||||||
setNpType(ntp);
|
|
||||||
props.onChange(pronoun);
|
|
||||||
} else {
|
|
||||||
props.onChange(undefined);
|
|
||||||
setNpType(ntp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const isDynamicComplement = props.np && props.np.type === "noun" && props.np.dynamicComplement;
|
|
||||||
const clearButton = <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>;
|
|
||||||
return <div>
|
|
||||||
{!npType && <div className="text-center mt-3">
|
|
||||||
{/* <div className="h6 mr-3">
|
|
||||||
Choose NP
|
|
||||||
</div> */}
|
|
||||||
{npTypes.map((npt) => <div className="mb-2">
|
|
||||||
<button
|
|
||||||
key={npt}
|
|
||||||
type="button"
|
|
||||||
className="mr-2 btn btn-sm btn-outline-secondary"
|
|
||||||
onClick={() => handleNPTypeChange(npt)}
|
|
||||||
>
|
|
||||||
{npt}
|
|
||||||
</button>
|
|
||||||
</div>)}
|
|
||||||
</div>}
|
|
||||||
{(npType === "pronoun" && props.np?.type === "pronoun")
|
|
||||||
? <PronounPicker
|
|
||||||
asObject={props.asObject}
|
|
||||||
pronoun={props.np}
|
|
||||||
onChange={props.onChange}
|
|
||||||
clearButton={clearButton}
|
|
||||||
opts={props.opts}
|
|
||||||
/>
|
|
||||||
: npType === "noun"
|
|
||||||
? <NounPicker
|
|
||||||
{..."getNounByTs" in props ? {
|
|
||||||
nouns: props.nouns,
|
|
||||||
getNounByTs: props.getNounByTs,
|
|
||||||
} : {
|
|
||||||
nouns: props.nouns,
|
|
||||||
}}
|
|
||||||
noun={(props.np && props.np.type === "noun") ? props.np : undefined}
|
|
||||||
onChange={props.onChange}
|
|
||||||
clearButton={!isDynamicComplement ? clearButton : undefined}
|
|
||||||
opts={props.opts}
|
|
||||||
/>
|
|
||||||
: npType === "participle"
|
|
||||||
? <ParticiplePicker
|
|
||||||
{..."getVerbByTs" in props ? {
|
|
||||||
verbs: props.verbs,
|
|
||||||
getVerbByTs: props.getVerbByTs,
|
|
||||||
} : {
|
|
||||||
verbs: props.verbs,
|
|
||||||
}}
|
|
||||||
participle={(props.np && props.np.type === "participle") ? props.np : undefined}
|
|
||||||
onChange={props.onChange}
|
|
||||||
clearButton={clearButton}
|
|
||||||
opts={props.opts}
|
|
||||||
/>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// {(npType && !isDynamicComplement) && }
|
|
||||||
|
|
||||||
export default NPPicker;
|
|
|
@ -1,149 +0,0 @@
|
||||||
import {
|
|
||||||
Types as T,
|
|
||||||
ButtonSelect,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import useStickyState from "../../useStickyState";
|
|
||||||
import classNames from "classnames";
|
|
||||||
|
|
||||||
const gColors = {
|
|
||||||
masc: "LightSkyBlue",
|
|
||||||
fem: "pink",
|
|
||||||
};
|
|
||||||
|
|
||||||
const labels = (asObject: boolean) => ({
|
|
||||||
persons: [
|
|
||||||
["1st", "1st pl."],
|
|
||||||
["2nd", "2nd pl."],
|
|
||||||
["3rd", "3rd pl."],
|
|
||||||
],
|
|
||||||
e: asObject ? [
|
|
||||||
["me", "us"],
|
|
||||||
["you", "you pl."],
|
|
||||||
[{ masc: "him/it", fem: "her/it"}, "them"],
|
|
||||||
] : [
|
|
||||||
["I", "We"],
|
|
||||||
["You", "You pl."],
|
|
||||||
[{ masc: "He/It", fem: "She/It"}, "They"],
|
|
||||||
],
|
|
||||||
p: {
|
|
||||||
far: [
|
|
||||||
["زه", "مونږ"],
|
|
||||||
["ته", "تاسو"],
|
|
||||||
["هغه", "هغوي"],
|
|
||||||
],
|
|
||||||
near: [
|
|
||||||
["زه", "مونږ"],
|
|
||||||
["ته", "تاسو"],
|
|
||||||
[{ masc: "دی", fem: "دا" }, "دوي"],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
type PickerState = { row: number, col: number, gender: T.Gender };
|
|
||||||
|
|
||||||
function personToPickerState(person: T.Person): PickerState {
|
|
||||||
const col = person > 5 ? 1 : 0;
|
|
||||||
const row = Math.floor((person > 5 ? (person - 6) : person) / 2);
|
|
||||||
const gender: T.Gender = (person % 2) ? "fem" : "masc";
|
|
||||||
return { col, row, gender };
|
|
||||||
}
|
|
||||||
|
|
||||||
function pickerStateToPerson(s: PickerState): T.Person {
|
|
||||||
return (s.row * 2)
|
|
||||||
+ (s.gender === "masc" ? 0 : 1)
|
|
||||||
+ (6 * s.col);
|
|
||||||
}
|
|
||||||
|
|
||||||
function NPPronounPicker({ onChange, pronoun, asObject, clearButton, opts }: {
|
|
||||||
pronoun: PronounSelection,
|
|
||||||
onChange: (p: PronounSelection) => void,
|
|
||||||
asObject?: boolean,
|
|
||||||
clearButton?: JSX.Element,
|
|
||||||
opts: T.TextOptions,
|
|
||||||
}) {
|
|
||||||
const [display, setDisplay] = useStickyState<"persons" | "p" | "e">("persons", "prounoun-picker-display");
|
|
||||||
|
|
||||||
const p = personToPickerState(pronoun.person);
|
|
||||||
function handleClick(row: number, col: number) {
|
|
||||||
const person = pickerStateToPerson({ ...p, row, col });
|
|
||||||
onChange({
|
|
||||||
...pronoun,
|
|
||||||
person,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function handleGenderChange(gender: T.Gender) {
|
|
||||||
const person = pickerStateToPerson({ ...p, gender });
|
|
||||||
onChange({
|
|
||||||
...pronoun,
|
|
||||||
person,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function handlePronounTypeChange(distance: "far" | "near") {
|
|
||||||
onChange({
|
|
||||||
...pronoun,
|
|
||||||
distance,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function handleDisplayChange() {
|
|
||||||
const newPerson = display === "persons"
|
|
||||||
? "p"
|
|
||||||
: display === "p"
|
|
||||||
? "e"
|
|
||||||
: "persons";
|
|
||||||
setDisplay(newPerson);
|
|
||||||
}
|
|
||||||
const prs = labels(!!asObject)[display];
|
|
||||||
const pSpec = "near" in prs ? prs[pronoun.distance] : prs;
|
|
||||||
return <div style={{ maxWidth: "145px", padding: 0 }}>
|
|
||||||
{clearButton}
|
|
||||||
<div className="d-flex flex-row justify-content-around mb-3">
|
|
||||||
<ButtonSelect
|
|
||||||
xSmall
|
|
||||||
options={[
|
|
||||||
{ label: "Far", value: "far" },
|
|
||||||
{ label: "Near", value: "near" },
|
|
||||||
]}
|
|
||||||
value={pronoun.distance}
|
|
||||||
handleChange={(g) => handlePronounTypeChange(g as "far" | "near")}
|
|
||||||
/>
|
|
||||||
<button className="btn btn-sm btn-outline" onClick={handleDisplayChange}>{display === "persons" ? "#" : display === "p" ? "PS" : "EN"}</button>
|
|
||||||
</div>
|
|
||||||
<table className="table table-bordered table-sm" style={{ textAlign: "center", minWidth: "100px", tableLayout: "fixed" }}>
|
|
||||||
<tbody>
|
|
||||||
{pSpec.map((rw, i) => (
|
|
||||||
<tr>
|
|
||||||
{rw.map((r, j) => {
|
|
||||||
const active = (p.row === i && p.col === j)
|
|
||||||
return <td
|
|
||||||
onClick={() => handleClick(i, j)}
|
|
||||||
className={classNames({ "table-active": active })}
|
|
||||||
style={{
|
|
||||||
backgroundColor: active ? gColors[p.gender] : "inherit",
|
|
||||||
padding: "0.25rem 0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="my-1">
|
|
||||||
{typeof r === "string" ? r : r[p.gender]}
|
|
||||||
</div>
|
|
||||||
</td>;
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div className="text-center">
|
|
||||||
<ButtonSelect
|
|
||||||
small
|
|
||||||
options={[
|
|
||||||
{ label: <div style={{ margin: "0.15rem"}}>Masc.</div>, value: "masc", color: gColors.masc },
|
|
||||||
{ label: <div style={{ margin: "0.15rem"}}>Fem.</div>, value: "fem", color: gColors.fem },
|
|
||||||
]}
|
|
||||||
value={p.gender}
|
|
||||||
handleChange={(g) => handleGenderChange(g as T.Gender)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NPPronounPicker;
|
|
|
@ -1,109 +0,0 @@
|
||||||
import {
|
|
||||||
isPluralNounEntry,
|
|
||||||
isMascNounEntry,
|
|
||||||
isUnisexNounEntry,
|
|
||||||
isVerbEntry,
|
|
||||||
} from "../../lib/type-predicates";
|
|
||||||
import {
|
|
||||||
getEnglishParticiple,
|
|
||||||
getEnglishVerb,
|
|
||||||
} from "../../lib/np-tools";
|
|
||||||
import {
|
|
||||||
getEnglishWord,
|
|
||||||
removeFVarients,
|
|
||||||
Types as T,
|
|
||||||
phoneticsToDiacritics,
|
|
||||||
convertSpelling,
|
|
||||||
translatePhonetics,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
|
|
||||||
export const zIndexProps = {
|
|
||||||
menuPortalTarget: document.body,
|
|
||||||
styles: { menuPortal: (base: any) => ({ ...base, zIndex: 9999 }) },
|
|
||||||
};
|
|
||||||
|
|
||||||
export function makeVerbSelectOption(e: VerbEntry, opts: T.TextOptions): { value: string, label: string | JSX.Element } {
|
|
||||||
const eng = getEnglishVerb(e.entry);
|
|
||||||
const ps = plainTextPsAdjustment(
|
|
||||||
{ p: e.entry.p, f: removeFVarients(e.entry.f) },
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
label: `${ps.p} - ${ps.f} (${eng})`,
|
|
||||||
value: e.entry.ts.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function plainTextPsAdjustment(ps: T.PsString, opts: T.TextOptions): T.PsString {
|
|
||||||
function getP(ps: T.PsString): string {
|
|
||||||
const p = opts.diacritics
|
|
||||||
? (phoneticsToDiacritics(ps.p, ps.f) || ps.p)
|
|
||||||
: ps.p;
|
|
||||||
return convertSpelling(p, opts.spelling);
|
|
||||||
}
|
|
||||||
function getF(f: string): string {
|
|
||||||
if (opts.phonetics === "none") {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return opts.phonetics === "lingdocs"
|
|
||||||
? f
|
|
||||||
: translatePhonetics(f, {
|
|
||||||
dialect: opts.dialect,
|
|
||||||
// @ts-ignore - weird TS not picking up the elimination of "none herre"
|
|
||||||
system: opts.phonetics,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return { p: getP(ps), f: getF(ps.f) };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeSelectOption(
|
|
||||||
e: T.DictionaryEntry | VerbEntry | NounEntry | AdjectiveEntry | LocativeAdverbEntry,
|
|
||||||
opts: T.TextOptions,
|
|
||||||
): { value: string, label: string } {
|
|
||||||
const entry = "entry" in e ? e.entry : e;
|
|
||||||
const eng = (isVerbEntry(e))
|
|
||||||
? (getEnglishParticiple(e.entry))
|
|
||||||
: getEnglishWord(e);
|
|
||||||
const english = typeof eng === "string"
|
|
||||||
? eng
|
|
||||||
: !eng
|
|
||||||
? ""
|
|
||||||
: ("singular" in eng && eng.singular !== undefined)
|
|
||||||
? eng.singular
|
|
||||||
: eng.plural;
|
|
||||||
const ps = plainTextPsAdjustment(
|
|
||||||
{ p: entry.p, f: removeFVarients(entry.f) },
|
|
||||||
opts,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
label: `${ps.p} - ${ps.f} (${english})`,
|
|
||||||
value: entry.ts.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeNounSelection(entry: NounEntry, dynamicComplement?: true): NounSelection {
|
|
||||||
const number = isPluralNounEntry(entry) ? "plural" : "singular";
|
|
||||||
return {
|
|
||||||
type: "noun",
|
|
||||||
entry,
|
|
||||||
gender: isMascNounEntry(entry) ? "masc" : "fem",
|
|
||||||
number,
|
|
||||||
dynamicComplement,
|
|
||||||
...isUnisexNounEntry(entry) ? {
|
|
||||||
changeGender: function(gender: T.Gender): NounSelection {
|
|
||||||
return {
|
|
||||||
...this,
|
|
||||||
gender,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
} : {},
|
|
||||||
...number === "singular" ? {
|
|
||||||
changeNumber: function(number: NounNumber): NounSelection {
|
|
||||||
return {
|
|
||||||
...this,
|
|
||||||
number,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
} : {},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
import {
|
|
||||||
ButtonSelect,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
|
|
||||||
const options = [
|
|
||||||
{
|
|
||||||
label: "Full",
|
|
||||||
value: "full",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "🚫 King",
|
|
||||||
value: "noKing",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "👶 Servant",
|
|
||||||
value: "shrinkServant",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "👶 🚫 Both",
|
|
||||||
value: "shortest",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function formToValue(f: FormVersion) {
|
|
||||||
if (f.removeKing === false && f.shrinkServant === false) {
|
|
||||||
return "full";
|
|
||||||
}
|
|
||||||
if (f.removeKing === true && f.shrinkServant === false) {
|
|
||||||
return "noKing";
|
|
||||||
}
|
|
||||||
if (f.removeKing === false && f.shrinkServant === true) {
|
|
||||||
return "shrinkServant";
|
|
||||||
}
|
|
||||||
if (f.removeKing === true && f.shrinkServant === true) {
|
|
||||||
return "shortest";
|
|
||||||
}
|
|
||||||
throw new Error("unrecognized abbreviation form");
|
|
||||||
}
|
|
||||||
|
|
||||||
function limitOptions(adjustable: "both" | "king" | "servant") {
|
|
||||||
if (adjustable === "both") {
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
if (adjustable === "king") {
|
|
||||||
return options.filter(o => !["shrinkServant", "shortest"].includes(o.value));
|
|
||||||
}
|
|
||||||
if (adjustable === "servant") {
|
|
||||||
return options.filter(o => !["noKing", "shortest"].includes(o.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function limitValue(value: string, adjustable: "both" | "king" | "servant") {
|
|
||||||
if (adjustable === "both") return value;
|
|
||||||
if (adjustable === "king") {
|
|
||||||
return (value === "shortest")
|
|
||||||
? "noKing"
|
|
||||||
: (value === "shrinkServant")
|
|
||||||
? "full"
|
|
||||||
: value;
|
|
||||||
}
|
|
||||||
if (adjustable === "servant") {
|
|
||||||
return (value === "shortest")
|
|
||||||
? "shrinkServant"
|
|
||||||
: (value === "noKing")
|
|
||||||
? "full"
|
|
||||||
: value;
|
|
||||||
}
|
|
||||||
throw new Error("unrecognized adjustable value");
|
|
||||||
}
|
|
||||||
|
|
||||||
function AbbreviationFormSelector({ form, onChange, adjustable }: {
|
|
||||||
form: FormVersion,
|
|
||||||
onChange: (f: FormVersion) => void,
|
|
||||||
adjustable: "both" | "king" | "servant",
|
|
||||||
}) {
|
|
||||||
function handleChange(f: "full" | "noKing" | "shrinkServant" | "shortest") {
|
|
||||||
if (f === "full") {
|
|
||||||
onChange({ removeKing: false, shrinkServant: false });
|
|
||||||
} else if (f === "noKing") {
|
|
||||||
onChange({ removeKing: true, shrinkServant: false });
|
|
||||||
} else if (f === "shrinkServant") {
|
|
||||||
onChange({ removeKing: false, shrinkServant: true });
|
|
||||||
} else if (f === "shortest") {
|
|
||||||
onChange({ removeKing: true, shrinkServant: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: limit display of shrinking options based on the verb type
|
|
||||||
return <div className="mb-3">
|
|
||||||
{/* <div className="text-center text-small mb-2">Abbreviation Options</div> */}
|
|
||||||
<ButtonSelect
|
|
||||||
small
|
|
||||||
// @ts-ignore
|
|
||||||
value={limitValue(formToValue(form), adjustable)}
|
|
||||||
// @ts-ignore
|
|
||||||
options={limitOptions(adjustable)}
|
|
||||||
// @ts-ignore
|
|
||||||
handleChange={handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AbbreviationFormSelector;
|
|
|
@ -1,31 +0,0 @@
|
||||||
import {
|
|
||||||
conjugateVerb,
|
|
||||||
VerbFormDisplay,
|
|
||||||
Types as T,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import {
|
|
||||||
getTenseVerbForm,
|
|
||||||
} from "../../lib/phrase-building/vp-tools";
|
|
||||||
|
|
||||||
function ChartDisplay({ VS, opts }: { VS: VerbSelection, opts: T.TextOptions }) {
|
|
||||||
const rawConjugations = conjugateVerb(VS.verb.entry, VS.verb.complement);
|
|
||||||
if (!rawConjugations) {
|
|
||||||
return <div>Error conjugating verb</div>;
|
|
||||||
}
|
|
||||||
const conjugations = ("stative" in rawConjugations)
|
|
||||||
? rawConjugations[VS.isCompound === "stative" ? "stative" : "dynamic"]
|
|
||||||
: ("transitive" in rawConjugations)
|
|
||||||
? rawConjugations[VS.transitivity === "grammatically transitive" ? "grammaticallyTransitive" : "transitive"]
|
|
||||||
: rawConjugations;
|
|
||||||
const form = getTenseVerbForm(conjugations, VS.tense, VS.tenseCategory, VS.voice);
|
|
||||||
return <div className="mb-4">
|
|
||||||
<VerbFormDisplay
|
|
||||||
displayForm={form}
|
|
||||||
showingFormInfo={false}
|
|
||||||
textOptions={opts}
|
|
||||||
info={conjugations.info}
|
|
||||||
/>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ChartDisplay;
|
|
|
@ -1,206 +0,0 @@
|
||||||
import NPPicker from "../np-picker/NPPicker";
|
|
||||||
import VerbPicker from "../VerbPicker";
|
|
||||||
import TensePicker from "../TensePicker";
|
|
||||||
import VPDisplay from "./VPDisplay";
|
|
||||||
import { verbs } from "../../words/words";
|
|
||||||
import { renderVP } from "../../lib/phrase-building";
|
|
||||||
import {
|
|
||||||
isInvalidSubjObjCombo,
|
|
||||||
} from "../../lib/np-tools";
|
|
||||||
import {
|
|
||||||
ButtonSelect,
|
|
||||||
defaultTextOptions,
|
|
||||||
Types as T,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import ChartDisplay from "./ChartDisplay";
|
|
||||||
import useStickyState from "../../useStickyState";
|
|
||||||
import { makeVerbSelection } from "./verb-selection";
|
|
||||||
|
|
||||||
const kingEmoji = "👑";
|
|
||||||
const servantEmoji = "🙇♂️";
|
|
||||||
|
|
||||||
// TODO: Drill Down text display options
|
|
||||||
|
|
||||||
// TODO: SHOW KING AND SERVANT ONCE TENSE PICKED, EVEN IF NPs not selected
|
|
||||||
// TODO: Issue with dynamic compounds english making with plurals
|
|
||||||
// TODO: Issue with "the money were taken"
|
|
||||||
// TODO: Use the same component for PronounPicker and NPPronounPicker (sizing issue)
|
|
||||||
// get the practice pronoun picker page into a typesafe file
|
|
||||||
// A little button you can press on the tense select to show the formula and info about the tense
|
|
||||||
// in a popup
|
|
||||||
// TODO: option to show 3 modes Phrases - Charts - Quiz
|
|
||||||
|
|
||||||
// TODO: error handling on error with rendering etc
|
|
||||||
export function PhraseBuilder(props: {
|
|
||||||
verb?: VerbEntry,
|
|
||||||
opts: T.TextOptions,
|
|
||||||
} & ({
|
|
||||||
nouns: NounEntry[],
|
|
||||||
verbs: VerbEntry[],
|
|
||||||
} | {
|
|
||||||
nouns: (s: string) => NounEntry[],
|
|
||||||
verbs: (s: string) => VerbEntry[],
|
|
||||||
getNounByTs: (ts: number) => NounEntry | undefined,
|
|
||||||
getVerbByTs: (ts: number) => VerbEntry | undefined,
|
|
||||||
})) {
|
|
||||||
const [subject, setSubject] = useStickyState<NPSelection | undefined>(undefined, "subjectNPSelection");
|
|
||||||
const [mode, setMode] = useStickyState<"charts" | "phrases">("phrases", "verbExplorerMode");
|
|
||||||
const passedVerb = props.verb;
|
|
||||||
const [verb, setVerb] = useStickyState<VerbSelection | undefined>(
|
|
||||||
passedVerb
|
|
||||||
? (old) => makeVerbSelection(passedVerb, setSubject, old)
|
|
||||||
: undefined,
|
|
||||||
"verbExplorerVerb",
|
|
||||||
);
|
|
||||||
const textOpts = props.opts || defaultTextOptions;
|
|
||||||
function handleSubjectChange(subject: NPSelection | undefined, skipPronounConflictCheck?: boolean) {
|
|
||||||
if (!skipPronounConflictCheck && hasPronounConflict(subject, verb?.object)) {
|
|
||||||
alert("That combination of pronouns is not allowed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSubject(subject);
|
|
||||||
}
|
|
||||||
function handleObjectChange(object: NPSelection | undefined) {
|
|
||||||
if (!verb) return;
|
|
||||||
if ((verb.object === "none") || (typeof verb.object === "number")) return;
|
|
||||||
// check for pronoun conflict
|
|
||||||
if (hasPronounConflict(subject, object)) {
|
|
||||||
alert("That combination of pronouns is not allowed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setVerb({ ...verb, object });
|
|
||||||
}
|
|
||||||
function handleSubjObjSwap() {
|
|
||||||
if (verb?.isCompound === "dynamic") return;
|
|
||||||
const output = switchSubjObj({ subject, verb });
|
|
||||||
setSubject(output.subject);
|
|
||||||
setVerb(output.verb);
|
|
||||||
}
|
|
||||||
const verbPhrase: VPSelection | undefined = verbPhraseComplete({ subject, verb });
|
|
||||||
const VPRendered = verbPhrase && renderVP(verbPhrase);
|
|
||||||
return <div className="mt-3" style={{ maxWidth: "950px"}}>
|
|
||||||
<VerbPicker
|
|
||||||
verbLocked={!!props.verb}
|
|
||||||
verbs={verbs}
|
|
||||||
verb={verb}
|
|
||||||
subject={subject}
|
|
||||||
changeSubject={(s) => handleSubjectChange(s, true)}
|
|
||||||
onChange={setVerb}
|
|
||||||
opts={textOpts}
|
|
||||||
/>
|
|
||||||
<div className="text-right mb-2">
|
|
||||||
<ButtonSelect
|
|
||||||
value={mode}
|
|
||||||
options={[
|
|
||||||
{ label: "Charts", value: "charts" },
|
|
||||||
{ label: "Phrases", value: "phrases" },
|
|
||||||
]}
|
|
||||||
handleChange={setMode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{(verb && (typeof verb.object === "object") && (verb.isCompound !== "dynamic") && (mode !== "charts")) &&
|
|
||||||
<div className="text-center mt-4">
|
|
||||||
<button onClick={handleSubjObjSwap} className="btn btn-sm btn-light">
|
|
||||||
<i className="fas fa-exchange-alt mr-2" /> subj/obj
|
|
||||||
</button>
|
|
||||||
</div>}
|
|
||||||
<div className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
|
|
||||||
{mode !== "charts" && <>
|
|
||||||
<div className="my-2">
|
|
||||||
<div className="h5 text-center">Subject {showRole(VPRendered, "subject")}</div>
|
|
||||||
<NPPicker
|
|
||||||
{..."getNounByTs" in props ? {
|
|
||||||
getNounByTs: props.getNounByTs,
|
|
||||||
getVerbByTs: props.getVerbByTs,
|
|
||||||
nouns: props.nouns,
|
|
||||||
verbs: props.verbs,
|
|
||||||
} : {
|
|
||||||
nouns: props.nouns,
|
|
||||||
verbs: props.verbs,
|
|
||||||
}}
|
|
||||||
np={subject}
|
|
||||||
counterPart={verb ? verb.object : undefined}
|
|
||||||
onChange={handleSubjectChange}
|
|
||||||
opts={textOpts}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{verb && (verb.object !== "none") && <div className="my-2">
|
|
||||||
<div className="h5 text-center">Object {showRole(VPRendered, "object")}</div>
|
|
||||||
{(typeof verb.object === "number")
|
|
||||||
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
|
|
||||||
: <NPPicker
|
|
||||||
{..."getNounByTs" in props ? {
|
|
||||||
getNounByTs: props.getNounByTs,
|
|
||||||
getVerbByTs: props.getVerbByTs,
|
|
||||||
nouns: props.nouns,
|
|
||||||
verbs: props.verbs,
|
|
||||||
} : {
|
|
||||||
nouns: props.nouns,
|
|
||||||
verbs: props.verbs,
|
|
||||||
}}
|
|
||||||
asObject
|
|
||||||
np={verb.object}
|
|
||||||
counterPart={subject}
|
|
||||||
onChange={handleObjectChange}
|
|
||||||
opts={textOpts}
|
|
||||||
/>}
|
|
||||||
</div>}
|
|
||||||
</>}
|
|
||||||
<div className="my-2">
|
|
||||||
<TensePicker
|
|
||||||
verbs={verbs}
|
|
||||||
verb={verb}
|
|
||||||
onChange={setVerb}
|
|
||||||
mode={mode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{(verbPhrase && (mode === "phrases")) &&
|
|
||||||
<VPDisplay VP={verbPhrase} opts={textOpts} />
|
|
||||||
}
|
|
||||||
{(verb && (mode === "charts")) && <ChartDisplay VS={verb} opts={textOpts} />}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PhraseBuilder;
|
|
||||||
|
|
||||||
function hasPronounConflict(subject: NPSelection | undefined, object: undefined | VerbObject): boolean {
|
|
||||||
const subjPronoun = (subject && subject.type === "pronoun") ? subject : undefined;
|
|
||||||
const objPronoun = (object && typeof object === "object" && object.type === "pronoun") ? object : undefined;
|
|
||||||
if (!subjPronoun || !objPronoun) return false;
|
|
||||||
return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person);
|
|
||||||
}
|
|
||||||
|
|
||||||
function verbPhraseComplete({ subject, verb }: { subject: NPSelection | undefined, verb: VerbSelection | undefined }): VPSelection | undefined {
|
|
||||||
if (!subject) return undefined;
|
|
||||||
if (!verb) return undefined;
|
|
||||||
if (verb.object === undefined) return undefined;
|
|
||||||
return {
|
|
||||||
type: "VPSelection",
|
|
||||||
subject,
|
|
||||||
object: verb.object,
|
|
||||||
verb,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function showRole(VP: VPRendered | undefined, member: "subject" | "object") {
|
|
||||||
return VP
|
|
||||||
? <span className="ml-2">
|
|
||||||
{(VP.king === member ? kingEmoji : VP.servant === member ? servantEmoji : "")}
|
|
||||||
</span>
|
|
||||||
: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
type SOClump = { subject: NPSelection | undefined, verb: VerbSelection | undefined };
|
|
||||||
function switchSubjObj({ subject, verb }: SOClump): SOClump {
|
|
||||||
if (!subject|| !verb || !verb.object || !(typeof verb.object === "object")) {
|
|
||||||
return { subject, verb };
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
subject: verb.object,
|
|
||||||
verb: {
|
|
||||||
...verb,
|
|
||||||
object: subject,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
import { useState } from "react";
|
|
||||||
import { renderVP, compileVP } from "../../lib/phrase-building";
|
|
||||||
import {
|
|
||||||
InlinePs,
|
|
||||||
Types as T,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import AbbreviationFormSelector from "./AbbreviationFormSelector";
|
|
||||||
import { isPastTense } from "../../lib/phrase-building/vp-tools";
|
|
||||||
|
|
||||||
function VPDisplay({ VP, opts }: { VP: VPSelection, opts: T.TextOptions }) {
|
|
||||||
const [form, setForm] = useState<FormVersion>({ removeKing: false, shrinkServant: false });
|
|
||||||
const [OSV, setOSV] = useState<boolean>(false);
|
|
||||||
const result = compileVP(renderVP(VP), { ...form, OSV });
|
|
||||||
return <div className="text-center mt-2">
|
|
||||||
{VP.verb.transitivity === "transitive" && <div className="form-check mb-2">
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
checked={OSV}
|
|
||||||
id="OSVCheckbox"
|
|
||||||
onChange={e => setOSV(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label text-muted" htmlFor="OSVCheckbox">
|
|
||||||
Include O S V
|
|
||||||
</label>
|
|
||||||
</div>}
|
|
||||||
<AbbreviationFormSelector
|
|
||||||
adjustable={whatsAdjustable(VP)}
|
|
||||||
form={form}
|
|
||||||
onChange={setForm}
|
|
||||||
/>
|
|
||||||
{"long" in result.ps ?
|
|
||||||
<div>
|
|
||||||
{/* <div className="h6">Long Verb:</div> */}
|
|
||||||
<VariationLayer vs={result.ps.long} opts={opts} />
|
|
||||||
{/* <div className="h6">Short Verb:</div> */}
|
|
||||||
<VariationLayer vs={result.ps.short} opts={opts} />
|
|
||||||
{result.ps.mini && <>
|
|
||||||
{/* <div className="h6">Mini Verb:</div> */}
|
|
||||||
<VariationLayer vs={result.ps.mini} opts={opts} />
|
|
||||||
</>}
|
|
||||||
</div>
|
|
||||||
: <VariationLayer vs={result.ps} opts={opts} />
|
|
||||||
}
|
|
||||||
{result.e && <div className="text-muted">
|
|
||||||
{result.e.map((e, i) => <div key={i}>{e}</div>)}
|
|
||||||
</div>}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
function whatsAdjustable(VP: VPSelection): "both" | "king" | "servant" {
|
|
||||||
// TODO: intransitive dynamic compounds?
|
|
||||||
return (VP.verb.isCompound === "dynamic" && VP.verb.transitivity === "transitive")
|
|
||||||
? (isPastTense(VP.verb.tense) ? "servant" : "king")
|
|
||||||
: VP.verb.transitivity === "transitive"
|
|
||||||
? "both"
|
|
||||||
: VP.verb.transitivity === "intransitive"
|
|
||||||
? "king"
|
|
||||||
// grammTrans
|
|
||||||
: isPastTense(VP.verb.tense)
|
|
||||||
? "servant"
|
|
||||||
: "king";
|
|
||||||
}
|
|
||||||
|
|
||||||
function VariationLayer({ vs, opts }: { vs: T.PsString[], opts: T.TextOptions }) {
|
|
||||||
return <div className="mb-2">
|
|
||||||
{vs.map((r, i) => <div key={i}>
|
|
||||||
<InlinePs opts={opts}>{r}</InlinePs>
|
|
||||||
</div>)}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default VPDisplay;
|
|
|
@ -1,108 +0,0 @@
|
||||||
import {
|
|
||||||
makeNounSelection,
|
|
||||||
} from "../np-picker/picker-tools";
|
|
||||||
import {
|
|
||||||
getVerbInfo,
|
|
||||||
Types as T,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import { isPerfectTense } from "../../lib/phrase-building/vp-tools";
|
|
||||||
|
|
||||||
export function makeVerbSelection(verb: VerbEntry, changeSubject: (s: NPSelection | undefined) => void, oldVerbSelection?: VerbSelection): VerbSelection {
|
|
||||||
const info = getVerbInfo(verb.entry, verb.complement);
|
|
||||||
function getTransObjFromOldVerbSelection() {
|
|
||||||
if (
|
|
||||||
!oldVerbSelection ||
|
|
||||||
oldVerbSelection.object === "none" ||
|
|
||||||
typeof oldVerbSelection.object === "number" ||
|
|
||||||
oldVerbSelection.isCompound === "dynamic" ||
|
|
||||||
(oldVerbSelection.object?.type === "noun" && oldVerbSelection.object.dynamicComplement)
|
|
||||||
) return undefined;
|
|
||||||
return oldVerbSelection.object;
|
|
||||||
}
|
|
||||||
const transitivity: T.Transitivity = "grammaticallyTransitive" in info
|
|
||||||
? "transitive"
|
|
||||||
: info.transitivity;
|
|
||||||
const object = (transitivity === "grammatically transitive")
|
|
||||||
? T.Person.ThirdPlurMale
|
|
||||||
: (info.type === "dynamic compound" && oldVerbSelection?.voice !== "passive")
|
|
||||||
? makeNounSelection(info.objComplement.entry as NounEntry, true)
|
|
||||||
: (transitivity === "transitive" && oldVerbSelection?.voice !== "passive")
|
|
||||||
? getTransObjFromOldVerbSelection()
|
|
||||||
: "none";
|
|
||||||
if (oldVerbSelection?.voice === "passive" && info.type === "dynamic compound") {
|
|
||||||
changeSubject(makeNounSelection(info.objComplement.entry as NounEntry, true));
|
|
||||||
}
|
|
||||||
const isCompound = ("stative" in info || info.type === "stative compound")
|
|
||||||
? "stative"
|
|
||||||
: info.type === "dynamic compound"
|
|
||||||
? "dynamic"
|
|
||||||
: false;
|
|
||||||
// TODO: here and below in the changeStatDyn function ... allow for entries with complement
|
|
||||||
const dynAuxVerb: VerbEntry | undefined = isCompound !== "dynamic"
|
|
||||||
? undefined
|
|
||||||
: info.type === "dynamic compound"
|
|
||||||
? { entry: info.auxVerb } as VerbEntry
|
|
||||||
: "dynamic" in info
|
|
||||||
? { entry: info.dynamic.auxVerb } as VerbEntry
|
|
||||||
: undefined;
|
|
||||||
const tenseSelection = ((): { tenseCategory: "perfect", tense: PerfectTense } | {
|
|
||||||
tenseCategory: "basic" | "modal",
|
|
||||||
tense: VerbTense,
|
|
||||||
} => {
|
|
||||||
if (!oldVerbSelection) {
|
|
||||||
return { tense: "presentVerb", tenseCategory: "basic" };
|
|
||||||
}
|
|
||||||
if (oldVerbSelection.tenseCategory === "modal") {
|
|
||||||
return { tenseCategory: "modal", tense: isPerfectTense(oldVerbSelection.tense) ? "presentVerb" : oldVerbSelection.tense };
|
|
||||||
}
|
|
||||||
if (oldVerbSelection.tenseCategory === "basic") {
|
|
||||||
return { tenseCategory: "basic", tense: isPerfectTense(oldVerbSelection.tense) ? "presentVerb" : oldVerbSelection.tense };
|
|
||||||
}
|
|
||||||
return { tenseCategory: "perfect", tense: isPerfectTense(oldVerbSelection.tense) ? oldVerbSelection.tense : "present perfect" };
|
|
||||||
})();
|
|
||||||
return {
|
|
||||||
type: "verb",
|
|
||||||
verb: verb,
|
|
||||||
dynAuxVerb,
|
|
||||||
...tenseSelection,
|
|
||||||
object,
|
|
||||||
transitivity,
|
|
||||||
isCompound,
|
|
||||||
voice: transitivity === "transitive"
|
|
||||||
? (oldVerbSelection?.voice || "active")
|
|
||||||
: "active",
|
|
||||||
negative: oldVerbSelection ? oldVerbSelection.negative : false,
|
|
||||||
...("grammaticallyTransitive" in info) ? {
|
|
||||||
changeTransitivity: function(t) {
|
|
||||||
return {
|
|
||||||
...this,
|
|
||||||
transitivity: t,
|
|
||||||
object: t === "grammatically transitive" ? T.Person.ThirdPlurMale : undefined,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
} : {},
|
|
||||||
...("stative" in info) ? {
|
|
||||||
changeStatDyn: function(c) {
|
|
||||||
return {
|
|
||||||
...this,
|
|
||||||
isCompound: c,
|
|
||||||
object: c === "dynamic"
|
|
||||||
? makeNounSelection(info.dynamic.objComplement.entry as NounEntry, true)
|
|
||||||
: undefined,
|
|
||||||
dynAuxVerb: c === "dynamic"
|
|
||||||
? { entry: info.dynamic.auxVerb } as VerbEntry
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} : {},
|
|
||||||
...(transitivity === "transitive") ? {
|
|
||||||
changeVoice: function(v, s) {
|
|
||||||
return {
|
|
||||||
...this,
|
|
||||||
voice: v,
|
|
||||||
object: v === "active" ? s : "none",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
} : {},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
removeFVarients,
|
removeFVarients,
|
||||||
InflectionsTable,
|
InflectionsTable,
|
||||||
inflectWord,
|
inflectWord,
|
||||||
|
typePredicates as tp,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import shuffle from "../../lib/shuffle-array";
|
import shuffle from "../../lib/shuffle-array";
|
||||||
import InflectionCarousel from "../../components/InflectionCarousel";
|
import InflectionCarousel from "../../components/InflectionCarousel";
|
||||||
|
@ -29,12 +30,8 @@ import { nouns } from "../../words/words";
|
||||||
import Link from "../../components/Link";
|
import Link from "../../components/Link";
|
||||||
import Table from "../../components/Table";
|
import Table from "../../components/Table";
|
||||||
import { startingWord } from "../../lib/starting-word";
|
import { startingWord } from "../../lib/starting-word";
|
||||||
import {
|
|
||||||
isFemNounEntry,
|
|
||||||
isPattern6FemEntry,
|
|
||||||
} from "../../lib/type-predicates";
|
|
||||||
|
|
||||||
export const femNouns = nouns.filter(isFemNounEntry);
|
export const femNouns = nouns.filter(tp.isFemNounEntry);
|
||||||
|
|
||||||
The <Link to="/inflection/inflection-patterns/">5 basic patterns in the last chapter</Link> work with both masculine and feminine words (nouns and adjectives).
|
The <Link to="/inflection/inflection-patterns/">5 basic patterns in the last chapter</Link> work with both masculine and feminine words (nouns and adjectives).
|
||||||
|
|
||||||
|
@ -42,6 +39,6 @@ There are also a few more patterns that are only for **feminine nouns**.
|
||||||
|
|
||||||
## Feminine Nouns Ending in <InlinePs opts={opts} ps={{ p: "ي", f: "ee" }} />
|
## Feminine Nouns Ending in <InlinePs opts={opts} ps={{ p: "ي", f: "ee" }} />
|
||||||
|
|
||||||
<InflectionCarousel items={startingWord(femNouns.filter(isPattern6FemEntry), "آزادي")} />
|
<InflectionCarousel items={startingWord(femNouns.filter(tp.isPattern6FemEntry), "آزادي")} />
|
||||||
|
|
||||||
**Note:** This only works with **inanimate nouns**. (ie. words for people like <InlinePs opts={opts} ps={{ p: "باجي", f: "baajée", e: "older sister" }} /> will not inflect like this.)
|
**Note:** This only works with **inanimate nouns**. (ie. words for people like <InlinePs opts={opts} ps={{ p: "باجي", f: "baajée", e: "older sister" }} /> will not inflect like this.)
|
||||||
|
|
|
@ -21,24 +21,15 @@ import {
|
||||||
grammarUnits,
|
grammarUnits,
|
||||||
InflectionsTable,
|
InflectionsTable,
|
||||||
inflectWord,
|
inflectWord,
|
||||||
|
typePredicates as tp,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import {
|
|
||||||
isNounOrVerb,
|
|
||||||
isPattern1Entry,
|
|
||||||
isPattern2Entry,
|
|
||||||
isPattern3Entry,
|
|
||||||
isPattern4Entry,
|
|
||||||
isPattern5Entry,
|
|
||||||
isUnisexNounEntry,
|
|
||||||
} from "../../lib/type-predicates";
|
|
||||||
import InflectionCarousel from "../../components/InflectionCarousel";
|
import InflectionCarousel from "../../components/InflectionCarousel";
|
||||||
import { nouns, adjectives } from "../../words/words";
|
import { nouns, adjectives } from "../../words/words";
|
||||||
import { startingWord } from "../../lib/starting-word";
|
import { startingWord } from "../../lib/starting-word";
|
||||||
import Link from "../../components/Link";
|
import Link from "../../components/Link";
|
||||||
import psmd from "../../lib/psmd";
|
import psmd from "../../lib/psmd";
|
||||||
|
|
||||||
export const words = [
|
export const words = [
|
||||||
...nouns.filter(isUnisexNounEntry),
|
...nouns.filter(tp.isUnisexNounEntry),
|
||||||
...adjectives,
|
...adjectives,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -58,7 +49,7 @@ These words always end in:
|
||||||
- **Feminine:** - <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} />
|
- **Feminine:** - <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} />
|
||||||
|
|
||||||
<InflectionCarousel items={startingWord(
|
<InflectionCarousel items={startingWord(
|
||||||
words.filter(isPattern1Entry),
|
words.filter(tp.isPattern1Entry),
|
||||||
"غټ",
|
"غټ",
|
||||||
)} />
|
)} />
|
||||||
|
|
||||||
|
@ -93,7 +84,7 @@ In any other kind of sandwich the first inflection would be used.
|
||||||
## 2. Words ending in an unstressed <InlinePs opts={opts} ps={{ p: "ی", f: "ey" }} />
|
## 2. Words ending in an unstressed <InlinePs opts={opts} ps={{ p: "ی", f: "ey" }} />
|
||||||
|
|
||||||
<InflectionCarousel items={startingWord(
|
<InflectionCarousel items={startingWord(
|
||||||
words.filter(isPattern2Entry),
|
words.filter(tp.isPattern2Entry),
|
||||||
"ستړی",
|
"ستړی",
|
||||||
)} />
|
)} />
|
||||||
|
|
||||||
|
@ -102,7 +93,7 @@ In any other kind of sandwich the first inflection would be used.
|
||||||
This is very similar to pattern #2, but with the stress on the last syllable the feminine inflection changes.
|
This is very similar to pattern #2, but with the stress on the last syllable the feminine inflection changes.
|
||||||
|
|
||||||
<InflectionCarousel items={startingWord(
|
<InflectionCarousel items={startingWord(
|
||||||
words.filter(isPattern3Entry),
|
words.filter(tp.isPattern3Entry),
|
||||||
"لومړی",
|
"لومړی",
|
||||||
)} />
|
)} />
|
||||||
|
|
||||||
|
@ -114,7 +105,7 @@ These words are a little irregular but you can see a common patten based around:
|
||||||
- shortening the other forms and adding the <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} />, <InlinePs opts={opts} ps={{ p: "ـې", f: "-e" }} />, <InlinePs opts={opts} ps={{ p: "ـو", f: "-o" }} /> endings
|
- shortening the other forms and adding the <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} />, <InlinePs opts={opts} ps={{ p: "ـې", f: "-e" }} />, <InlinePs opts={opts} ps={{ p: "ـو", f: "-o" }} /> endings
|
||||||
|
|
||||||
<InflectionCarousel items={startingWord(
|
<InflectionCarousel items={startingWord(
|
||||||
words.filter(isPattern4Entry),
|
words.filter(tp.isPattern4Entry),
|
||||||
"پښتون",
|
"پښتون",
|
||||||
)} />
|
)} />
|
||||||
|
|
||||||
|
@ -157,7 +148,7 @@ But it will of course inflect for the plural.
|
||||||
These are also a little irregular but instead of lengthening the 1st masculine inflection they compress it as well and take just an <InlinePs opts={opts} ps={{ p: "ـه", f: "-u" }} /> on the end.
|
These are also a little irregular but instead of lengthening the 1st masculine inflection they compress it as well and take just an <InlinePs opts={opts} ps={{ p: "ـه", f: "-u" }} /> on the end.
|
||||||
|
|
||||||
<InflectionCarousel items={startingWord(
|
<InflectionCarousel items={startingWord(
|
||||||
words.filter(isPattern5Entry),
|
words.filter(tp.isPattern5Entry),
|
||||||
"غل",
|
"غل",
|
||||||
)} />
|
)} />
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@ import {
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
InlinePs,
|
InlinePs,
|
||||||
Examples,
|
Examples,
|
||||||
|
firstVariation,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import genderColors from "../../lib/gender-colors";
|
import genderColors from "../../lib/gender-colors";
|
||||||
import { firstVariation } from "../../lib/text-tools";
|
|
||||||
import GenderTable from "../../components/GenderTable";
|
import GenderTable from "../../components/GenderTable";
|
||||||
import Link from "../../components/Link";
|
import Link from "../../components/Link";
|
||||||
export const femColor = genderColors.f;
|
export const femColor = genderColors.f;
|
||||||
|
|
|
@ -3,13 +3,13 @@ title: Phrase Builder
|
||||||
fullWidth: true
|
fullWidth: true
|
||||||
---
|
---
|
||||||
|
|
||||||
import PhraseBuilder from "../../components/phrase-builder/PhraseBuilder";
|
|
||||||
import {
|
import {
|
||||||
|
VPExplorer,
|
||||||
defaultTextOptions,
|
defaultTextOptions,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import { nouns, verbs } from "../../words/words";
|
import { nouns, verbs } from "../../words/words";
|
||||||
|
|
||||||
<PhraseBuilder
|
<VPExplorer
|
||||||
opts={defaultTextOptions}
|
opts={defaultTextOptions}
|
||||||
nouns={nouns}
|
nouns={nouns}
|
||||||
verbs={verbs}
|
verbs={verbs}
|
||||||
|
|
|
@ -2,18 +2,16 @@
|
||||||
title: Pronoun Picker
|
title: Pronoun Picker
|
||||||
---
|
---
|
||||||
|
|
||||||
import PronounPicker from "../../components/np-picker/PronounPicker";
|
import PronounPicker from "../../components/PronounPicker";
|
||||||
import {
|
import {
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
InlinePs,
|
InlinePs,
|
||||||
ButtonSelect,
|
ButtonSelect,
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import { useState } from "react";
|
|
||||||
import {
|
|
||||||
randomPerson,
|
randomPerson,
|
||||||
randomSubjObj,
|
randomSubjObj,
|
||||||
isInvalidSubjObjCombo,
|
isInvalidSubjObjCombo,
|
||||||
} from "../../lib/np-tools";
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export function RPicker() {
|
export function RPicker() {
|
||||||
// TODO: Pull this into a type safe file
|
// TODO: Pull this into a type safe file
|
||||||
|
|
|
@ -33,7 +33,7 @@ import verbTreeImperfectivePerfective from "./verb-tree-imperfective-perfective.
|
||||||
|
|
||||||
export const opts = defaultTextOptions;
|
export const opts = defaultTextOptions;
|
||||||
|
|
||||||
export const verbs = verbsRaw.filter(v => !v.c?.includes("comp."));
|
export const verbs = verbsRaw.filter(v => !("complement" in v));
|
||||||
|
|
||||||
export function InfoCarousel({ items, highlighted, hidePastParticiple }) {
|
export function InfoCarousel({ items, highlighted, hidePastParticiple }) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -12,44 +12,38 @@ import {
|
||||||
pashtoConsonants,
|
pashtoConsonants,
|
||||||
inflectWord,
|
inflectWord,
|
||||||
isUnisexSet,
|
isUnisexSet,
|
||||||
|
typePredicates as tp,
|
||||||
|
firstVariation,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import { nouns } from "../../words/words";
|
import { nouns } from "../../words/words";
|
||||||
import {
|
|
||||||
firstVariation,
|
|
||||||
} from "../../lib/text-tools";
|
|
||||||
import {
|
|
||||||
isMascNounEntry,
|
|
||||||
isFemNounEntry,
|
|
||||||
isUnisexNounEntry,
|
|
||||||
} from "../../lib/type-predicates";
|
|
||||||
import { categorize } from "../../lib/categorize";
|
import { categorize } from "../../lib/categorize";
|
||||||
|
|
||||||
const genders: T.Gender[] = ["masc", "fem"];
|
const genders: T.Gender[] = ["masc", "fem"];
|
||||||
|
|
||||||
const mascNouns = nouns.filter(isMascNounEntry);
|
const mascNouns = nouns.filter(tp.isMascNounEntry);
|
||||||
const femNouns = [
|
const femNouns = [
|
||||||
...nouns.filter(isFemNounEntry),
|
...nouns.filter(tp.isFemNounEntry),
|
||||||
...getFemVersions(mascNouns.filter(isUnisexNounEntry)),
|
...getFemVersions(mascNouns.filter(tp.isUnisexNounEntry)),
|
||||||
];
|
];
|
||||||
|
|
||||||
const types = {
|
const types = {
|
||||||
masc: categorize<MascNounEntry, {
|
masc: categorize<T.MascNounEntry, {
|
||||||
consonantMasc: MascNounEntry[],
|
consonantMasc: T.MascNounEntry[],
|
||||||
eyMasc: MascNounEntry[],
|
eyMasc: T.MascNounEntry[],
|
||||||
uMasc: MascNounEntry[],
|
uMasc: T.MascNounEntry[],
|
||||||
yMasc: MascNounEntry[],
|
yMasc: T.MascNounEntry[],
|
||||||
}>(mascNouns, {
|
}>(mascNouns, {
|
||||||
consonantMasc: endsWith([{ p: pashtoConsonants }, { p: "و", f: "w" }]),
|
consonantMasc: endsWith([{ p: pashtoConsonants }, { p: "و", f: "w" }]),
|
||||||
eyMasc: endsWith({ p: "ی", f: "ey" }),
|
eyMasc: endsWith({ p: "ی", f: "ey" }),
|
||||||
uMasc: endsWith({ p: "ه", f: "u" }),
|
uMasc: endsWith({ p: "ه", f: "u" }),
|
||||||
yMasc: endsWith([{ p: "ای", f: "aay" }, { p: "وی", f: "ooy" }]),
|
yMasc: endsWith([{ p: "ای", f: "aay" }, { p: "وی", f: "ooy" }]),
|
||||||
}),
|
}),
|
||||||
fem: categorize<FemNounEntry, {
|
fem: categorize<T.FemNounEntry, {
|
||||||
aaFem: FemNounEntry[],
|
aaFem: T.FemNounEntry[],
|
||||||
eeFem: FemNounEntry[],
|
eeFem: T.FemNounEntry[],
|
||||||
uyFem: FemNounEntry[],
|
uyFem: T.FemNounEntry[],
|
||||||
aFem: FemNounEntry[],
|
aFem: T.FemNounEntry[],
|
||||||
eFem: FemNounEntry[],
|
eFem: T.FemNounEntry[],
|
||||||
}>(femNouns, {
|
}>(femNouns, {
|
||||||
aaFem: endsWith({ p: "ا", f: "aa" }),
|
aaFem: endsWith({ p: "ا", f: "aa" }),
|
||||||
eeFem: endsWith({ p: "ي", f: "ee" }),
|
eeFem: endsWith({ p: "ي", f: "ee" }),
|
||||||
|
@ -59,7 +53,7 @@ const types = {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFemVersions(uns: UnisexNounEntry[]): FemNounEntry[] {
|
function getFemVersions(uns: T.UnisexNounEntry[]): T.FemNounEntry[] {
|
||||||
return uns.map((n) => {
|
return uns.map((n) => {
|
||||||
const infs = inflectWord(n);
|
const infs = inflectWord(n);
|
||||||
if (!infs || !infs.inflections) return undefined;
|
if (!infs || !infs.inflections) return undefined;
|
||||||
|
@ -68,18 +62,18 @@ function getFemVersions(uns: UnisexNounEntry[]): FemNounEntry[] {
|
||||||
e: n.e,
|
e: n.e,
|
||||||
...infs.inflections.fem[0][0],
|
...infs.inflections.fem[0][0],
|
||||||
} as T.DictionaryEntry;
|
} as T.DictionaryEntry;
|
||||||
}).filter(n => !!n) as FemNounEntry[];
|
}).filter(n => !!n) as T.FemNounEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function flatten<T>(o: Record<string, T[]>): T[] {
|
function flatten<T>(o: Record<string, T[]>): T[] {
|
||||||
return Object.values(o).flat();
|
return Object.values(o).flat();
|
||||||
}
|
}
|
||||||
|
|
||||||
function nounNotIn(st: NounEntry[]): (n: NounEntry | T.DictionaryEntry) => boolean {
|
function nounNotIn(st: T.NounEntry[]): (n: T.NounEntry | T.DictionaryEntry) => boolean {
|
||||||
return (n: T.DictionaryEntry) => !st.find(x => x.ts === n.ts);
|
return (n: T.DictionaryEntry) => !st.find(x => x.ts === n.ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
type CategorySet = Record<string, NounEntry[]>;
|
type CategorySet = Record<string, T.NounEntry[]>;
|
||||||
// for some reason we need to use this CategorySet type here... 🤷♂️
|
// for some reason we need to use this CategorySet type here... 🤷♂️
|
||||||
const exceptions: Record<string, CategorySet> = {
|
const exceptions: Record<string, CategorySet> = {
|
||||||
masc: {
|
masc: {
|
||||||
|
|
|
@ -12,17 +12,13 @@ import {
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
inflectWord,
|
inflectWord,
|
||||||
standardizePashto,
|
standardizePashto,
|
||||||
|
firstVariation,
|
||||||
|
typePredicates as tp,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import { nouns } from "../../words/words";
|
import { nouns } from "../../words/words";
|
||||||
import { intoPatterns } from "../../lib/categorize";
|
import { intoPatterns } from "../../lib/categorize";
|
||||||
import {
|
|
||||||
firstVariation,
|
|
||||||
} from "../../lib/text-tools";
|
|
||||||
import {
|
|
||||||
isUnisexNounEntry,
|
|
||||||
} from "../../lib/type-predicates";
|
|
||||||
|
|
||||||
const unisexNouns = nouns.filter(isUnisexNounEntry);
|
const unisexNouns = nouns.filter(tp.isUnisexNounEntry);
|
||||||
type NType = "pattern1" | "pattern2" | "pattern3" | "pattern4" | "pattern5" | "other";
|
type NType = "pattern1" | "pattern2" | "pattern3" | "pattern4" | "pattern5" | "other";
|
||||||
// TODO: make pattern types as overlay types
|
// TODO: make pattern types as overlay types
|
||||||
const types = intoPatterns(unisexNouns);
|
const types = intoPatterns(unisexNouns);
|
||||||
|
@ -41,7 +37,7 @@ export default function UnisexNounGame({ id, link }: { id: string, link: string
|
||||||
do {
|
do {
|
||||||
type = getRandomFromList(keys);
|
type = getRandomFromList(keys);
|
||||||
} while (!pool[type].length);
|
} while (!pool[type].length);
|
||||||
const entry = getRandomFromList<UnisexNounEntry>(
|
const entry = getRandomFromList<T.UnisexNounEntry>(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
pool[type]
|
pool[type]
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import {
|
import {
|
||||||
|
typePredicates,
|
||||||
|
// super weird, need to es-lint disable this this
|
||||||
|
// eslint-disable-next-line
|
||||||
|
Types as T,
|
||||||
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
const {
|
||||||
isPattern1Entry,
|
isPattern1Entry,
|
||||||
isPattern2Entry,
|
isPattern2Entry,
|
||||||
isPattern3Entry,
|
isPattern3Entry,
|
||||||
isPattern4Entry,
|
isPattern4Entry,
|
||||||
isPattern5Entry,
|
isPattern5Entry,
|
||||||
} from "./type-predicates";
|
} = typePredicates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sorts a given array of on type into a typed object of arrays of subtypes, based on predicates
|
* sorts a given array of on type into a typed object of arrays of subtypes, based on predicates
|
||||||
|
@ -75,22 +81,22 @@ export function categorize<I, X extends Record<string, I[]>>(
|
||||||
|
|
||||||
// TODO: uncategorizable words like ایرې - n. f. pl. -- could be pattern 1 or 2 🤷♂️
|
// TODO: uncategorizable words like ایرې - n. f. pl. -- could be pattern 1 or 2 🤷♂️
|
||||||
|
|
||||||
export function intoPatterns<T extends (NounEntry | AdjectiveEntry)>(words: T[]): {
|
export function intoPatterns<T extends (T.NounEntry | T.AdjectiveEntry)>(words: T[]): {
|
||||||
"pattern1": Pattern1Entry<T>[],
|
"pattern1": T.Pattern1Entry<T>[],
|
||||||
"pattern2": Pattern2Entry<T>[],
|
"pattern2": T.Pattern2Entry<T>[],
|
||||||
"pattern3": Pattern3Entry<T>[],
|
"pattern3": T.Pattern3Entry<T>[],
|
||||||
"pattern4": Pattern4Entry<T>[],
|
"pattern4": T.Pattern4Entry<T>[],
|
||||||
"pattern5": Pattern5Entry<T>[],
|
"pattern5": T.Pattern5Entry<T>[],
|
||||||
"other": NonInflecting<T>[],
|
"other": T.NonInflecting<T>[],
|
||||||
// "pattern6fem": Pattern6FemNoun<T>[],
|
// "pattern6fem": Pattern6FemNoun<T>[],
|
||||||
} {
|
} {
|
||||||
return categorize<(NounEntry | AdjectiveEntry), {
|
return categorize<(T.NounEntry | T.AdjectiveEntry), {
|
||||||
"pattern1": Pattern1Entry<T>[],
|
"pattern1": T.Pattern1Entry<T>[],
|
||||||
"pattern2": Pattern2Entry<T>[],
|
"pattern2": T.Pattern2Entry<T>[],
|
||||||
"pattern3": Pattern3Entry<T>[],
|
"pattern3": T.Pattern3Entry<T>[],
|
||||||
"pattern4": Pattern4Entry<T>[],
|
"pattern4": T.Pattern4Entry<T>[],
|
||||||
"pattern5": Pattern5Entry<T>[],
|
"pattern5": T.Pattern5Entry<T>[],
|
||||||
"other": NonInflecting<T>[],
|
"other": T.NonInflecting<T>[],
|
||||||
// "pattern6fem": Pattern6FemNoun<T>[],
|
// "pattern6fem": Pattern6FemNoun<T>[],
|
||||||
}>(
|
}>(
|
||||||
words,
|
words,
|
||||||
|
|
|
@ -5,11 +5,9 @@ import {
|
||||||
isUnisexSet,
|
isUnisexSet,
|
||||||
personGender,
|
personGender,
|
||||||
personIsPlural,
|
personIsPlural,
|
||||||
} from "@lingdocs/pashto-inflector";
|
typePredicates as tp,
|
||||||
import { isAdjectiveEntry, isLocativeAdverbEntry } from "./type-predicates";
|
|
||||||
import {
|
|
||||||
psStringFromEntry,
|
psStringFromEntry,
|
||||||
} from "./text-tools";
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
|
||||||
export function evaluateCompliment(c: Compliment, person: T.Person): { ps: T.PsString[], e: string } {
|
export function evaluateCompliment(c: Compliment, person: T.Person): { ps: T.PsString[], e: string } {
|
||||||
const e = getEnglishWord(c.entry);
|
const e = getEnglishWord(c.entry);
|
||||||
|
@ -17,13 +15,13 @@ export function evaluateCompliment(c: Compliment, person: T.Person): { ps: T.PsS
|
||||||
console.log(e);
|
console.log(e);
|
||||||
throw new Error("error getting english for compliment");
|
throw new Error("error getting english for compliment");
|
||||||
}
|
}
|
||||||
if (isLocativeAdverbEntry(c.entry)) {
|
if (tp.isLocativeAdverbEntry(c.entry)) {
|
||||||
return {
|
return {
|
||||||
ps: [psStringFromEntry(c.entry)],
|
ps: [psStringFromEntry(c.entry)],
|
||||||
e,
|
e,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (isAdjectiveEntry(c.entry)) {
|
if (tp.isAdjectiveEntry(c.entry)) {
|
||||||
const infs = inflectWord(c.entry);
|
const infs = inflectWord(c.entry);
|
||||||
if (!infs) return {
|
if (!infs) return {
|
||||||
ps: [psStringFromEntry(c.entry)],
|
ps: [psStringFromEntry(c.entry)],
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
getPersonFromVerbForm,
|
getPersonFromVerbForm,
|
||||||
concatPsString,
|
concatPsString,
|
||||||
removeAccents,
|
removeAccents,
|
||||||
|
typePredicates as tp,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import {
|
import {
|
||||||
personFromNP,
|
personFromNP,
|
||||||
|
@ -13,7 +14,6 @@ import {
|
||||||
import {
|
import {
|
||||||
evaluateCompliment,
|
evaluateCompliment,
|
||||||
} from "./compliment-tools";
|
} from "./compliment-tools";
|
||||||
import { isPluralNounEntry } from "./type-predicates";
|
|
||||||
|
|
||||||
// Equative Rules
|
// Equative Rules
|
||||||
// - An equative equates a SUBJECT: Noun Phrase and a PREDICATE: Noun Phrase | Compliment
|
// - An equative equates a SUBJECT: Noun Phrase and a PREDICATE: Noun Phrase | Compliment
|
||||||
|
@ -84,7 +84,7 @@ function makeEquative(e: EquativeClause) {
|
||||||
? "past"
|
? "past"
|
||||||
: e.tense;
|
: e.tense;
|
||||||
const subjP = personFromNP(e.subject);
|
const subjP = personFromNP(e.subject);
|
||||||
const englishPerson = (e.subject.type === "participle" || (e.subject.type === "noun" && isPluralNounEntry(e.subject.entry)))
|
const englishPerson = (e.subject.type === "participle" || (e.subject.type === "noun" && tp.isPluralNounEntry(e.subject.entry)))
|
||||||
? T.Person.ThirdSingMale
|
? T.Person.ThirdSingMale
|
||||||
: subjP
|
: subjP
|
||||||
const pashtoPerson = (e.subject.type === "pronoun")
|
const pashtoPerson = (e.subject.type === "pronoun")
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { isMascNounEntry, isPluralNounEntry, isUnisexNounEntry } from "./type-predicates";
|
|
||||||
import {
|
import {
|
||||||
Types as T,
|
Types as T,
|
||||||
getEnglishWord,
|
getEnglishWord,
|
||||||
|
@ -6,16 +5,15 @@ import {
|
||||||
getVerbBlockPosFromPerson,
|
getVerbBlockPosFromPerson,
|
||||||
grammarUnits,
|
grammarUnits,
|
||||||
inflectWord,
|
inflectWord,
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import {
|
|
||||||
psStringFromEntry,
|
psStringFromEntry,
|
||||||
} from "./text-tools";
|
typePredicates as tp,
|
||||||
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
|
||||||
function getRandPers(): T.Person {
|
function getRandPers(): T.Person {
|
||||||
return Math.floor(Math.random() * 12);
|
return Math.floor(Math.random() * 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function randomPerson(a?: { prev?: T.Person, counterPart?: VerbObject | NPSelection }) {
|
export function randomPerson(a?: { prev?: T.Person, counterPart?: T.VerbObject | T.NPSelection }) {
|
||||||
// no restrictions, just get any person
|
// no restrictions, just get any person
|
||||||
if (!a) {
|
if (!a) {
|
||||||
return getRandPers();
|
return getRandPers();
|
||||||
|
@ -99,9 +97,9 @@ export function evaluateNP(np: NounPhrase): { ps: T.PsString[], e: string } {
|
||||||
}
|
}
|
||||||
|
|
||||||
function nounGender(n: Noun): T.Gender {
|
function nounGender(n: Noun): T.Gender {
|
||||||
const nGender = isUnisexNounEntry(n.entry)
|
const nGender = tp.isUnisexNounEntry(n.entry)
|
||||||
? "unisex"
|
? "unisex"
|
||||||
: isMascNounEntry(n.entry)
|
: tp.isMascNounEntry(n.entry)
|
||||||
? "masc"
|
? "masc"
|
||||||
: "fem";
|
: "fem";
|
||||||
return (nGender === "unisex" && n.gender)
|
return (nGender === "unisex" && n.gender)
|
||||||
|
@ -112,7 +110,7 @@ function nounGender(n: Noun): T.Gender {
|
||||||
}
|
}
|
||||||
|
|
||||||
function nounNumber(n: Noun): NounNumber {
|
function nounNumber(n: Noun): NounNumber {
|
||||||
const nNumber = isPluralNounEntry(n.entry)
|
const nNumber = tp.isPluralNounEntry(n.entry)
|
||||||
? "plural"
|
? "plural"
|
||||||
: "singular";
|
: "singular";
|
||||||
return nNumber === "plural"
|
return nNumber === "plural"
|
||||||
|
@ -215,4 +213,3 @@ function getEnglishFromNoun(entry: T.DictionaryEntry, number: NounNumber): strin
|
||||||
}
|
}
|
||||||
return addArticle(e.singular);
|
return addArticle(e.singular);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,384 +0,0 @@
|
||||||
import {
|
|
||||||
Types as T,
|
|
||||||
concatPsString,
|
|
||||||
removeAccents,
|
|
||||||
grammarUnits,
|
|
||||||
getVerbBlockPosFromPerson,
|
|
||||||
makePsString,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import {
|
|
||||||
removeBa,
|
|
||||||
removeDuplicates,
|
|
||||||
} from "./vp-tools";
|
|
||||||
|
|
||||||
type Form = FormVersion & { OSV?: boolean };
|
|
||||||
export function compileVP(VP: VPRendered, form: Form): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string [] };
|
|
||||||
export function compileVP(VP: VPRendered, form: Form, combineLengths: true): { ps: T.PsString[], e?: string [] };
|
|
||||||
export function compileVP(VP: VPRendered, form: Form, combineLengths?: true): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string [] } {
|
|
||||||
const verb = VP.verb.ps;
|
|
||||||
const { kids, NPs } = getSegmentsAndKids(VP, form);
|
|
||||||
const psResult = compilePs({
|
|
||||||
NPs,
|
|
||||||
kids,
|
|
||||||
verb,
|
|
||||||
VP,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
ps: combineLengths ? flattenLengths(psResult) : psResult,
|
|
||||||
// TODO: English doesn't quite work for dynamic compounds in passive voice
|
|
||||||
e: (VP.verb.voice === "passive" && VP.isCompound === "dynamic") ? undefined : compileEnglish(VP),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompilePsInput = {
|
|
||||||
NPs: Segment[][],
|
|
||||||
kids: Segment[],
|
|
||||||
verb: {
|
|
||||||
head: T.PsString | undefined,
|
|
||||||
rest: T.SingleOrLengthOpts<T.PsString[]>,
|
|
||||||
},
|
|
||||||
VP: VPRendered,
|
|
||||||
}
|
|
||||||
function compilePs({ NPs, kids, verb: { head, rest }, VP }: CompilePsInput): T.SingleOrLengthOpts<T.PsString[]> {
|
|
||||||
if ("long" in rest) {
|
|
||||||
return {
|
|
||||||
long: compilePs({ NPs, verb: { head, rest: rest.long }, VP, kids }) as T.PsString[],
|
|
||||||
short: compilePs({ NPs, verb: { head, rest: rest.short }, VP, kids }) as T.PsString[],
|
|
||||||
...rest.mini ? {
|
|
||||||
mini: compilePs({ NPs, verb: { head, rest: rest.mini }, VP, kids }) as T.PsString[],
|
|
||||||
} : {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const verbWNegativeVersions = arrangeVerbWNegative(head, rest, VP.verb);
|
|
||||||
|
|
||||||
// put together all the different possible permutations based on:
|
|
||||||
// a. potential different versions of where the nu goes
|
|
||||||
return removeDuplicates(verbWNegativeVersions.flatMap((verbSegments) => (
|
|
||||||
// b. potential reordering of NPs
|
|
||||||
NPs.flatMap(NP => {
|
|
||||||
// for each permutation of the possible ordering of NPs and Verb + nu
|
|
||||||
// 1. put in kids in the kids section
|
|
||||||
const segments = putKidsInKidsSection([...NP, ...verbSegments], kids);
|
|
||||||
// 2. space out the words properly
|
|
||||||
const withProperSpaces = addSpacesBetweenSegments(segments);
|
|
||||||
// 3. throw it all together into a PsString for each permutation
|
|
||||||
return combineSegments(withProperSpaces);
|
|
||||||
})
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSegmentsAndKids(VP: VPRendered, form: Form): { kids: Segment[], NPs: Segment[][] } {
|
|
||||||
const SO = {
|
|
||||||
subject: VP.subject.ps,
|
|
||||||
object: typeof VP.object === "object" ? VP.object.ps : undefined,
|
|
||||||
}
|
|
||||||
const removeKing = form.removeKing && !(VP.isCompound === "dynamic" && VP.isPast);
|
|
||||||
const shrinkServant = form.shrinkServant && !(VP.isCompound === "dynamic" && !VP.isPast);
|
|
||||||
|
|
||||||
const toShrink = (() => {
|
|
||||||
if (!shrinkServant) return undefined;
|
|
||||||
if (!VP.servant) return undefined;
|
|
||||||
const servant = VP[VP.servant];
|
|
||||||
if (typeof servant !== "object") return undefined;
|
|
||||||
return servant;
|
|
||||||
})();
|
|
||||||
function getSegment(t: "subject" | "object"): Segment | undefined {
|
|
||||||
const word = (VP.servant === t)
|
|
||||||
? (!shrinkServant ? SO[t] : undefined)
|
|
||||||
: (VP.king === t)
|
|
||||||
? (!removeKing ? SO[t] : undefined)
|
|
||||||
: undefined;
|
|
||||||
if (!word) return undefined;
|
|
||||||
return makeSegment(word);
|
|
||||||
}
|
|
||||||
const subject = getSegment("subject");
|
|
||||||
const object = getSegment("object");
|
|
||||||
|
|
||||||
return {
|
|
||||||
kids: [
|
|
||||||
...VP.verb.hasBa
|
|
||||||
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] : [],
|
|
||||||
...toShrink
|
|
||||||
? [shrinkNP(toShrink)] : [],
|
|
||||||
],
|
|
||||||
NPs: [
|
|
||||||
[
|
|
||||||
...subject ? [subject] : [],
|
|
||||||
...object ? [object] : [],
|
|
||||||
],
|
|
||||||
// TODO: make this an option to also include O S V order ??
|
|
||||||
// also show O S V if both are showing
|
|
||||||
...(subject && object && form.OSV) ? [[object, subject]] : [],
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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: VerbRendered): Segment[][] {
|
|
||||||
const hasLeapfrog = V.tenseCategory === "modal" || V.tenseCategory === "perfect";
|
|
||||||
const rest = (() => {
|
|
||||||
if (hasLeapfrog) {
|
|
||||||
const [restF, restLast] = splitOffLeapfrogWord(restRaw);
|
|
||||||
return {
|
|
||||||
front: makeSegment(restF.map(removeBa), ["isVerbRest"]),
|
|
||||||
last: makeSegment(restLast.map(removeBa), ["isVerbRest"]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return makeSegment(restRaw.map(removeBa), ["isVerbRest"]);
|
|
||||||
})();
|
|
||||||
const headSegment: Segment | undefined = !head
|
|
||||||
? head
|
|
||||||
: makeSegment(
|
|
||||||
head,
|
|
||||||
(head.p === "و" || head.p === "وا")
|
|
||||||
? ["isVerbHead", "isOoOrWaaHead"]
|
|
||||||
: ["isVerbHead"]
|
|
||||||
);
|
|
||||||
if (!V.negative) {
|
|
||||||
if ("front" in rest) {
|
|
||||||
return [
|
|
||||||
headSegment ? [headSegment, rest.front, rest.last] : [rest.front, rest.last],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
headSegment ? [headSegment, rest] : [rest],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
const nu: T.PsString = { p: "نه", f: "nú" };
|
|
||||||
if (!headSegment) {
|
|
||||||
if ("front" in rest) {
|
|
||||||
return [
|
|
||||||
// pefect nu dey me leeduley and nu me dey leeduley
|
|
||||||
[
|
|
||||||
mergeSegments(
|
|
||||||
makeSegment(nu, ["isNu"]),
|
|
||||||
rest.last.adjust({ ps: removeAccents }),
|
|
||||||
),
|
|
||||||
rest.front.adjust({ ps: removeAccents }),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
makeSegment(nu, ["isNu"]),
|
|
||||||
rest.last.adjust({ ps: removeAccents }),
|
|
||||||
rest.front.adjust({ ps: removeAccents }),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
rest.front.adjust({ ps: removeAccents }),
|
|
||||||
makeSegment(nu, ["isNu"]),
|
|
||||||
rest.last.adjust({ ps: removeAccents }),
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return [[
|
|
||||||
makeSegment(nu, ["isNu"]),
|
|
||||||
rest.adjust({ ps: removeAccents }),
|
|
||||||
]];
|
|
||||||
}
|
|
||||||
if ("front" in rest) {
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
headSegment.adjust({ ps: removeAccents }),
|
|
||||||
rest.last.adjust({
|
|
||||||
ps: r => concatPsString(nu, " ", removeAccents(r)),
|
|
||||||
desc: ["isNu"],
|
|
||||||
}),
|
|
||||||
rest.front.adjust({
|
|
||||||
ps: r => removeAccents(r),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
headSegment.adjust({ ps: removeAccents }),
|
|
||||||
rest.front.adjust({
|
|
||||||
ps: r => concatPsString(nu, " ", removeAccents(r)),
|
|
||||||
desc: ["isNu"],
|
|
||||||
}),
|
|
||||||
rest.last.adjust({
|
|
||||||
ps: r => removeAccents(r),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
...(!headSegment.isOoOrWaaHead && !V.isCompound) ? [[
|
|
||||||
mergeSegments(headSegment, rest.front, "no space").adjust({
|
|
||||||
ps: r => concatPsString(nu, " ", removeAccents(r)),
|
|
||||||
desc: ["isNu"],
|
|
||||||
}),
|
|
||||||
rest.last.adjust({
|
|
||||||
ps: r => removeAccents(r),
|
|
||||||
}),
|
|
||||||
]] : [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
...(V.voice !== "passive") ? [[
|
|
||||||
...headSegment ? [headSegment.adjust({ ps: removeAccents })] : [],
|
|
||||||
rest.adjust({
|
|
||||||
ps: r => concatPsString(nu, " ", removeAccents(r)),
|
|
||||||
desc: ["isNu"],
|
|
||||||
}),
|
|
||||||
]] : [],
|
|
||||||
// verbs that have a perfective prefix that is not و or وا can put the
|
|
||||||
// nu *before* the prefix as well // TODO: also وي prefixes?
|
|
||||||
...((!headSegment.isOoOrWaaHead && !V.isCompound) || (V.voice === "passive")) ? [[
|
|
||||||
makeSegment(nu, ["isNu"]),
|
|
||||||
headSegment.adjust({ ps: removeAccents }),
|
|
||||||
rest.adjust({ ps: removeAccents }),
|
|
||||||
]] : [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function shrinkNP(np: Rendered<NPSelection>): Segment {
|
|
||||||
const [row, col] = getVerbBlockPosFromPerson(np.person);
|
|
||||||
return makeSegment(grammarUnits.pronouns.mini[row][col], ["isKid", "isMiniPronoun"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeSegments(s1: Segment, s2: Segment, noSpace?: "no space"): Segment {
|
|
||||||
if (noSpace) {
|
|
||||||
return s2.adjust({ ps: (p) => concatPsString(s1.ps[0], p) });
|
|
||||||
}
|
|
||||||
return s2.adjust({ ps: (p) => concatPsString(s1.ps[0], " ", p) });
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSpacesBetweenSegments(segments: Segment[]): (Segment | " " | "" | T.PsString)[] {
|
|
||||||
const o: (Segment | " " | "" | T.PsString)[] = [];
|
|
||||||
for (let i = 0; i < segments.length; i++) {
|
|
||||||
const current = segments[i];
|
|
||||||
const next = segments[i+1];
|
|
||||||
o.push(current);
|
|
||||||
if (!next) break;
|
|
||||||
if (
|
|
||||||
// stative compound part
|
|
||||||
!current.ps[0].p.endsWith(" ")
|
|
||||||
&&
|
|
||||||
(
|
|
||||||
(next.isKidBetweenHeadAndRest || next.isNu)
|
|
||||||
||
|
|
||||||
(next.isVerbRest && current.isKidBetweenHeadAndRest)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
o.push({
|
|
||||||
f: " ", // TODO: make this "-" in the right places
|
|
||||||
p: ((current.isVerbHead && (next.isMiniPronoun || next.isNu))
|
|
||||||
|| (current.isOoOrWaaHead && (next.isBa || next.isNu))) ? "" : " ", // or if its waa head
|
|
||||||
});
|
|
||||||
} else if (current.isVerbHead && next.isVerbRest) {
|
|
||||||
o.push("");
|
|
||||||
} else {
|
|
||||||
o.push(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileEnglish(VP: VPRendered): string[] | undefined {
|
|
||||||
function insertEWords(e: string, { subject, object }: { subject: string, object?: string }): string {
|
|
||||||
return e.replace("$SUBJ", subject).replace("$OBJ", object || "");
|
|
||||||
}
|
|
||||||
const engSubj = VP.subject.e || undefined;
|
|
||||||
const engObj = (typeof VP.object === "object" && VP.object.e) ? VP.object.e : undefined;
|
|
||||||
// require all English parts for making the English phrase
|
|
||||||
return (VP.englishBase && engSubj && (engObj || typeof VP.object !== "object"))
|
|
||||||
? VP.englishBase.map(e => insertEWords(e, {
|
|
||||||
subject: engSubj,
|
|
||||||
object: engObj,
|
|
||||||
}))
|
|
||||||
: 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)]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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[]]);
|
|
||||||
}
|
|
|
@ -1,240 +0,0 @@
|
||||||
import {
|
|
||||||
Types as T,
|
|
||||||
getVerbBlockPosFromPerson,
|
|
||||||
grammarUnits,
|
|
||||||
parseEc,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
|
|
||||||
function engHave(s: T.Person): string {
|
|
||||||
function isThirdPersonSing(p: T.Person): boolean {
|
|
||||||
return (
|
|
||||||
p === T.Person.ThirdSingMale ||
|
|
||||||
p === T.Person.ThirdSingFemale
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return isThirdPersonSing(s) ? "has" : "have";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderEnglishVPBase({ subjectPerson, object, vs }: {
|
|
||||||
subjectPerson: T.Person,
|
|
||||||
object: NPSelection | ObjectNP,
|
|
||||||
vs: VerbSelection,
|
|
||||||
}): string[] {
|
|
||||||
const ec = parseEc(vs.verb.entry.ec || "");
|
|
||||||
const ep = vs.verb.entry.ep;
|
|
||||||
function engEquative(tense: "past" | "present", s: T.Person): string {
|
|
||||||
const [row, col] = getVerbBlockPosFromPerson(s);
|
|
||||||
return grammarUnits.englishEquative[tense][row][col];
|
|
||||||
}
|
|
||||||
function engPresC(s: T.Person, ec: T.EnglishVerbConjugationEc | [string, string]): string {
|
|
||||||
function isThirdPersonSing(p: T.Person): boolean {
|
|
||||||
return (
|
|
||||||
p === T.Person.ThirdSingMale ||
|
|
||||||
p === T.Person.ThirdSingFemale
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return isThirdPersonSing(s) ? ec[1] : ec[0];
|
|
||||||
}
|
|
||||||
function isToBe(v: T.EnglishVerbConjugationEc): boolean {
|
|
||||||
return (v[2] === "being");
|
|
||||||
}
|
|
||||||
const futureEngBuilder: T.EnglishBuilder = (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ will${n ? " not" : ""} ${isToBe(ec) ? "be" : ec[0]}`,
|
|
||||||
]);
|
|
||||||
// TODO: Pull these out to a seperate entity and import it
|
|
||||||
const basicBuilders: Record<
|
|
||||||
VerbTense,
|
|
||||||
(s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => string[]
|
|
||||||
> = {
|
|
||||||
presentVerb: (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${isToBe(ec)
|
|
||||||
? `${engEquative("present", s)}${n ? " not" : ""}`
|
|
||||||
: `${n ? engPresC(s, ["don't", "doesn't"]) : ""} ${n ? ec[0] : engPresC(s, ec)}`}`,
|
|
||||||
`$SUBJ ${engEquative("present", s)}${n ? " not" : ""} ${ec[2]}`,
|
|
||||||
]),
|
|
||||||
subjunctiveVerb: (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`that $SUBJ ${n ? " won't" : " will"} ${isToBe(ec) ? "be" : ec[0]}`,
|
|
||||||
`should $SUBJ ${n ? " not" : ""} ${isToBe(ec) ? "be" : ec[0]}`,
|
|
||||||
]),
|
|
||||||
imperfectiveFuture: futureEngBuilder,
|
|
||||||
perfectiveFuture: futureEngBuilder,
|
|
||||||
imperfectivePast: (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
// - subj pastEquative (N && "not") ec.2 obj
|
|
||||||
`$SUBJ ${engEquative("past", s)}${n ? " not" : ""} ${ec[2]}`,
|
|
||||||
// - subj "would" (N && "not") ec.0 obj
|
|
||||||
`$SUBJ would${n ? " not" : ""} ${isToBe(ec) ? "be" : ec[0]}`,
|
|
||||||
// - subj pastEquative (N && "not") going to" ec.0 obj
|
|
||||||
`$SUBJ ${engEquative("past", s)}${n ? " not" : ""} going to ${isToBe(ec) ? "be" : ec[0]}`,
|
|
||||||
]),
|
|
||||||
perfectivePast: (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ${isToBe(ec)
|
|
||||||
? ` ${engEquative("past", s)}${n ? " not" : ""}`
|
|
||||||
: (n ? ` did not ${ec[0]}` : ` ${ec[3]}`)
|
|
||||||
}`
|
|
||||||
]),
|
|
||||||
habitualPerfectivePast: (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ would${n ? " not" : ""} ${isToBe(ec) ? "be" : ec[0]}`,
|
|
||||||
`$SUBJ used to${n ? " not" : ""} ${isToBe(ec) ? "be" : ec[0]}`,
|
|
||||||
]),
|
|
||||||
habitualImperfectivePast: (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ would${n ? " not" : ""} ${isToBe(ec) ? "be" : ec[0]}`,
|
|
||||||
`$SUBJ used to${n ? " not" : ""} ${isToBe(ec) ? "be" : ec[0]}`,
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
const modalBuilders: Record<
|
|
||||||
VerbTense,
|
|
||||||
(s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => string[]
|
|
||||||
> = {
|
|
||||||
presentVerb: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ can${n ? "'t" : ""} ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
]),
|
|
||||||
subjunctiveVerb: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`that $SUBJ can${n ? "'t" : ""} ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
]),
|
|
||||||
imperfectiveFuture: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ will${n ? " not" : ""} be able to ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
]),
|
|
||||||
perfectiveFuture: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ will${n ? " not" : ""} be able to ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
]),
|
|
||||||
imperfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${engEquative("past", s)} ${n ? " not" : ""} able to ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
`$SUBJ could${n ? " not" : ""} ${v[0]}`,
|
|
||||||
]),
|
|
||||||
perfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${engEquative("past", s)} ${n ? " not" : ""} able to ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
`$SUBJ could${n ? " not" : ""} ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
]),
|
|
||||||
habitualImperfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ used to ${n ? " not" : ""} be able to ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
`$SUBJ would ${n ? " not" : ""} be able to ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
]),
|
|
||||||
habitualPerfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ used to ${n ? " not" : ""} be able to ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
`$SUBJ would ${n ? " not" : ""} be able to ${isToBe(v) ? "be" : v[0]}`,
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
const perfectBuilders: Record<
|
|
||||||
PerfectTense,
|
|
||||||
(s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => string[]
|
|
||||||
> = {
|
|
||||||
"present perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${engHave(s)}${n ? " not" : ""} ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"past perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ had${n ? " not" : ""} ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"habitual perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${engHave(s)}${n ? " not" : ""} ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"subjunctive perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`that $SUBJ will have${n ? " not" : ""} ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"future perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ will${n ? " not" : ""} have ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"wouldBe perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ would${n ? " not" : ""} have ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"pastSubjunctive perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ would${n ? " not" : ""} have ${v[4]}`,
|
|
||||||
`$SUBJ should${n ? " not" : ""} have ${v[4]}`,
|
|
||||||
]),
|
|
||||||
}
|
|
||||||
const passiveBasicBuilders: Record<
|
|
||||||
VerbTense,
|
|
||||||
(s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => string[]
|
|
||||||
> = {
|
|
||||||
presentVerb: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${engEquative("present", s)}${n ? " not" : ""} being ${v[4]}`,
|
|
||||||
`$SUBJ ${engEquative("present", s)}${n ? " not" : ""} ${v[4]}`,
|
|
||||||
]),
|
|
||||||
subjunctiveVerb: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`that $SUBJ will${n ? " not" : ""} be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
imperfectiveFuture: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ will${n ? " not" : ""} be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
perfectiveFuture: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ will${n ? " not" : ""} be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
imperfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${engEquative("past", s)}${n ? " not" : ""} being ${v[4]}`,
|
|
||||||
`$SUBJ would${n ? " not" : ""} be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
perfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${engEquative("past", s)}${n ? " not" : ""} ${v[4]}`,
|
|
||||||
]),
|
|
||||||
habitualPerfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ would${n ? " not" : ""} be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
habitualImperfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ would${n ? " not" : ""} be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
const passivePerfectBuilders: Record<
|
|
||||||
PerfectTense,
|
|
||||||
(s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => string[]
|
|
||||||
> = {
|
|
||||||
"present perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${engHave(s)}${n ? " not" : ""} been ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"past perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ had${n ? " not" : ""} been ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"habitual perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${engHave(s)}${n ? " not" : ""} been ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"subjunctive perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`that $SUBJ will${n ? " not" : ""} have been ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"future perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ will${n ? " not" : ""} have been ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"wouldBe perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ will${n ? " not" : ""} have been ${v[4]}`,
|
|
||||||
]),
|
|
||||||
"pastSubjunctive perfect": (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ would${n ? " not" : ""} have been ${v[4]}`,
|
|
||||||
]),
|
|
||||||
}
|
|
||||||
const passiveModalBuilders: Record<
|
|
||||||
VerbTense,
|
|
||||||
(s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => string[]
|
|
||||||
> = {
|
|
||||||
presentVerb: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ can${n ? " not" : ""} be ${v[4]}`,
|
|
||||||
`$SUBJ ${engEquative("present", s)}${n ? " not" : ""} able to be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
subjunctiveVerb: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`that $SUBJ will${n ? " not" : ""} be able to be ${v[4]}`,
|
|
||||||
`that $SUBJ ${n ? " not" : ""} be able to be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
imperfectiveFuture: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ will${n ? " not" : ""} be able to be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
perfectiveFuture: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ will${n ? " not" : ""} be able to be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
imperfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ would${n ? " not" : ""} be able to be ${v[4]}`,
|
|
||||||
`$SUBJ ${engEquative("past", s)}${n ? " not" : ""} being able to be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
perfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ ${engEquative("past", s)}${n ? " not" : ""} able to be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
habitualPerfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ would${n ? " not" : ""} be able to be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
habitualImperfectivePast: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([
|
|
||||||
`$SUBJ would${n ? " not" : ""} be able to be ${v[4]}`,
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
const base = (
|
|
||||||
(vs.tenseCategory === "perfect")
|
|
||||||
? (vs.voice === "active" ? perfectBuilders : passivePerfectBuilders)[vs.tense]
|
|
||||||
: vs.tenseCategory === "basic"
|
|
||||||
? (vs.voice === "active" ? basicBuilders : passiveBasicBuilders)[vs.tense]
|
|
||||||
: (vs.voice === "active" ? modalBuilders : passiveModalBuilders)[vs.tense])(subjectPerson, ec, vs.negative);
|
|
||||||
return base.map(b => `${b}${typeof object === "object" ? " $OBJ" : ""}${ep ? ` ${ep}` : ""}`);
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { renderVP } from "./render-vp";
|
|
||||||
import { compileVP } from "./compile-vp";
|
|
||||||
|
|
||||||
export {
|
|
||||||
renderVP,
|
|
||||||
compileVP,
|
|
||||||
};
|
|
|
@ -1,344 +0,0 @@
|
||||||
import {
|
|
||||||
Types as T,
|
|
||||||
getVerbBlockPosFromPerson,
|
|
||||||
grammarUnits,
|
|
||||||
getEnglishWord,
|
|
||||||
inflectWord,
|
|
||||||
parseEc,
|
|
||||||
conjugateVerb,
|
|
||||||
concatPsString,
|
|
||||||
removeAccents,
|
|
||||||
getPersonNumber,
|
|
||||||
hasBaParticle,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import {
|
|
||||||
psStringFromEntry,
|
|
||||||
getLong,
|
|
||||||
} from "../text-tools";
|
|
||||||
import {
|
|
||||||
getPersonFromNP,
|
|
||||||
removeBa,
|
|
||||||
isPastTense,
|
|
||||||
isPerfectTense,
|
|
||||||
getTenseVerbForm,
|
|
||||||
} from "./vp-tools";
|
|
||||||
import { isPattern4Entry } from "../type-predicates";
|
|
||||||
import { renderEnglishVPBase } from "./english-vp-rendering";
|
|
||||||
|
|
||||||
// TODO: ISSUE GETTING SPLIT HEAD NOT MATCHING WITH FUTURE VERBS
|
|
||||||
|
|
||||||
export function renderVP(VP: VPSelection): VPRendered {
|
|
||||||
// Sentence Rules Logic
|
|
||||||
const isPast = isPastTense(VP.verb.tense);
|
|
||||||
const isTransitive = VP.object !== "none";
|
|
||||||
const { king, servant } = getKingAndServant(isPast, isTransitive);
|
|
||||||
const kingPerson = getPersonFromNP(VP[king]);
|
|
||||||
// TODO: more elegant way of handling this type safety
|
|
||||||
if (kingPerson === undefined) {
|
|
||||||
throw new Error("king of sentance does not exist");
|
|
||||||
}
|
|
||||||
const subjectPerson = getPersonFromNP(VP.subject);
|
|
||||||
const objectPerson = getPersonFromNP(VP.object);
|
|
||||||
// TODO: also don't inflect if it's a pattern one animate noun
|
|
||||||
const inflectSubject = isPast && isTransitive && !isMascSingAnimatePattern4(VP.subject);
|
|
||||||
const inflectObject = !isPast && isFirstOrSecondPersPronoun(VP.object);
|
|
||||||
// Render Elements
|
|
||||||
return {
|
|
||||||
type: "VPRendered",
|
|
||||||
king,
|
|
||||||
servant,
|
|
||||||
isPast,
|
|
||||||
isTransitive,
|
|
||||||
isCompound: VP.verb.isCompound,
|
|
||||||
subject: renderNPSelection(VP.subject, inflectSubject, false, "subject"),
|
|
||||||
object: renderNPSelection(VP.object, inflectObject, true, "object"),
|
|
||||||
verb: renderVerbSelection(VP.verb, kingPerson, objectPerson),
|
|
||||||
englishBase: renderEnglishVPBase({
|
|
||||||
subjectPerson,
|
|
||||||
object: VP.verb.isCompound === "dynamic" ? "none" : VP.object,
|
|
||||||
vs: VP.verb,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderNPSelection(NP: NPSelection, inflected: boolean, inflectEnglish: boolean, role: "subject"): Rendered<NPSelection>
|
|
||||||
export function renderNPSelection(NP: NPSelection | ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "object"): Rendered<NPSelection> | T.Person.ThirdPlurMale | "none";
|
|
||||||
export function renderNPSelection(NP: NPSelection | ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "subject" | "object"): Rendered<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: NounSelection, inflected: boolean): Rendered<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: PronounSelection, inflected: boolean, englishInflected: boolean): Rendered<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: ParticipleSelection, inflected: boolean): Rendered<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: VerbSelection, person: T.Person, objectPerson: T.Person | undefined): VerbRendered {
|
|
||||||
const v = vs.dynAuxVerb || vs.verb;
|
|
||||||
const conjugations = conjugateVerb(v.entry, v.complement);
|
|
||||||
// TODO: error handle this?
|
|
||||||
const conj = "grammaticallyTransitive" in conjugations
|
|
||||||
// if there's an option for grammatically transitive or transitive
|
|
||||||
// will default to transitive
|
|
||||||
? conjugations.transitive
|
|
||||||
: "stative" in conjugations
|
|
||||||
// TODO: option to manually select stative/dynamic
|
|
||||||
? conjugations.stative
|
|
||||||
: conjugations;
|
|
||||||
return {
|
|
||||||
...vs,
|
|
||||||
person,
|
|
||||||
...getPsVerbConjugation(conj, vs, person, objectPerson),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPsVerbConjugation(conj: T.VerbConjugation, vs: VerbSelection, person: T.Person, objectPerson: T.Person | undefined): {
|
|
||||||
ps: {
|
|
||||||
head: T.PsString | undefined,
|
|
||||||
rest: T.SingleOrLengthOpts<T.PsString[]>,
|
|
||||||
},
|
|
||||||
hasBa: boolean,
|
|
||||||
} {
|
|
||||||
const f = getTenseVerbForm(conj, vs.tense, vs.tenseCategory, vs.voice);
|
|
||||||
const block = getMatrixBlock(f, objectPerson, person);
|
|
||||||
const perfective = isPerfective(vs.tense);
|
|
||||||
const verbForm = getVerbFromBlock(block, person);
|
|
||||||
const hasBa = hasBaParticle(getLong(verbForm)[0]);
|
|
||||||
if (perfective) {
|
|
||||||
const past = isPastTense(vs.tense);
|
|
||||||
const splitInfo = conj.info[past ? "root" : "stem"].perfectiveSplit;
|
|
||||||
if (!splitInfo) return { ps: { head: undefined, rest: verbForm }, hasBa };
|
|
||||||
// TODO: Either solve this in the inflector or here, it seems silly (or redundant)
|
|
||||||
// to have a length option in the perfective split stem??
|
|
||||||
const [splitHead] = getLong(getMatrixBlock(splitInfo, objectPerson, person));
|
|
||||||
const ps = getHeadAndRest(splitHead, verbForm);
|
|
||||||
return {
|
|
||||||
hasBa,
|
|
||||||
ps,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return { hasBa, ps: { head: undefined, rest: verbForm }};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVerbFromBlock(block: T.SingleOrLengthOpts<T.VerbBlock>, person: T.Person): T.SingleOrLengthOpts<T.PsString[]> {
|
|
||||||
function grabFromBlock(b: T.VerbBlock, [row, col]: [ row: number, col: number ]): T.PsString[] {
|
|
||||||
return b[row][col];
|
|
||||||
}
|
|
||||||
const pos = getVerbBlockPosFromPerson(person);
|
|
||||||
if ("long" in block) {
|
|
||||||
return {
|
|
||||||
long: grabFromBlock(block.long, pos),
|
|
||||||
short: grabFromBlock(block.short, pos),
|
|
||||||
...block.mini ? {
|
|
||||||
mini: grabFromBlock(block.mini, pos),
|
|
||||||
} : {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return grabFromBlock(block, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHeadAndRest(head: T.PsString, rest: T.PsString[]): { head: T.PsString | undefined, rest: T.PsString[] };
|
|
||||||
function getHeadAndRest(head: T.PsString, rest: T.SingleOrLengthOpts<T.PsString[]>): { head: T.PsString | undefined, rest: T.SingleOrLengthOpts<T.PsString[]> };
|
|
||||||
function getHeadAndRest(head: T.PsString, rest: T.SingleOrLengthOpts<T.PsString[]>): { head: T.PsString | undefined, rest: T.SingleOrLengthOpts<T.PsString[]> } {
|
|
||||||
if ("long" in rest) {
|
|
||||||
return {
|
|
||||||
// whether or not to include the head (for irreg tlul) -- eww // TODO: make nicer?
|
|
||||||
head: removeBa(rest.long[0]).p.slice(0, head.p.length) === head.p
|
|
||||||
? head : undefined,
|
|
||||||
rest: {
|
|
||||||
long: getHeadAndRest(head, rest.long).rest,
|
|
||||||
short: getHeadAndRest(head, rest.short).rest,
|
|
||||||
...rest.mini ? {
|
|
||||||
mini: getHeadAndRest(head, rest.mini).rest,
|
|
||||||
} : {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let headMismatch = false;
|
|
||||||
const restM = rest.map((psRaw) => {
|
|
||||||
const ps = removeBa(psRaw);
|
|
||||||
const pMatches = removeAccents(ps.p.slice(0, head.p.length)) === head.p
|
|
||||||
const fMatches = removeAccents(ps.f.slice(0, head.f.length)) === removeAccents(head.f);
|
|
||||||
if (!(pMatches && fMatches)) {
|
|
||||||
headMismatch = true;
|
|
||||||
return psRaw;
|
|
||||||
// throw new Error(`split head does not match - ${JSON.stringify(ps)} ${JSON.stringify(head)}`);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
p: ps.p.slice(head.p.length),
|
|
||||||
f: ps.f.slice(head.f.length),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
head: headMismatch ? undefined : head,
|
|
||||||
rest: restM,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMatrixBlock<U>(f: {
|
|
||||||
mascSing: T.SingleOrLengthOpts<U>;
|
|
||||||
mascPlur: T.SingleOrLengthOpts<U>;
|
|
||||||
femSing: T.SingleOrLengthOpts<U>;
|
|
||||||
femPlur: T.SingleOrLengthOpts<U>;
|
|
||||||
} | T.SingleOrLengthOpts<U>, objectPerson: T.Person | undefined, kingPerson: T.Person): T.SingleOrLengthOpts<U> {
|
|
||||||
if (!("mascSing" in f)) {
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
function personToLabel(p: T.Person): "mascSing" | "mascPlur" | "femSing" | "femPlur" {
|
|
||||||
if (p === T.Person.FirstSingMale || p === T.Person.SecondSingMale || p === T.Person.ThirdSingMale) {
|
|
||||||
return "mascSing";
|
|
||||||
}
|
|
||||||
if (p === T.Person.FirstSingFemale || p === T.Person.SecondSingFemale || p === T.Person.ThirdSingFemale) {
|
|
||||||
return "femSing";
|
|
||||||
}
|
|
||||||
if (p === T.Person.FirstPlurMale || p === T.Person.SecondPlurMale || p === T.Person.ThirdPlurMale) {
|
|
||||||
return "mascPlur";
|
|
||||||
}
|
|
||||||
return "femPlur";
|
|
||||||
}
|
|
||||||
// if there's an object the matrix will agree with that, otherwise with the kingPerson (subject for intransitive)
|
|
||||||
const person = (objectPerson === undefined) ? kingPerson : objectPerson;
|
|
||||||
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: 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 [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getKingAndServant(isPast: boolean, isTransitive: boolean):
|
|
||||||
{ king: "subject", servant: "object" } |
|
|
||||||
{ king: "object", servant: "subject" } |
|
|
||||||
{ king: "subject", servant: undefined } {
|
|
||||||
if (!isTransitive) {
|
|
||||||
return { king: "subject", servant: undefined };
|
|
||||||
}
|
|
||||||
return isPast ? {
|
|
||||||
king: "object",
|
|
||||||
servant: "subject",
|
|
||||||
} : {
|
|
||||||
king: "subject",
|
|
||||||
servant: "object",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFirstOrSecondPersPronoun(o: "none" | NPSelection | T.Person.ThirdPlurMale): boolean {
|
|
||||||
if (typeof o !== "object") return false;
|
|
||||||
if (o.type !== "pronoun") return false;
|
|
||||||
return [0,1,2,3,6,7,8,9].includes(o.person);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPerfective(t: VerbTense | PerfectTense): boolean {
|
|
||||||
if (isPerfectTense(t)) return false;
|
|
||||||
if (t === "presentVerb" || t === "imperfectiveFuture" || t === "imperfectivePast" || t === "habitualImperfectivePast") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (t === "perfectiveFuture" || t === "subjunctiveVerb" || t === "perfectivePast" || t === "habitualPerfectivePast") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
throw new Error("tense not implemented yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMascSingAnimatePattern4(np: NPSelection): boolean {
|
|
||||||
if (np.type !== "noun") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return isPattern4Entry(np.entry)
|
|
||||||
&& np.entry.c.includes("anim.")
|
|
||||||
&& (np.number === "singular")
|
|
||||||
&& (np.gender === "masc");
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
import {
|
|
||||||
Types as T,
|
|
||||||
concatPsString,
|
|
||||||
grammarUnits,
|
|
||||||
psRemove,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
import { psStringEquals } from "@lingdocs/pashto-inflector/dist/lib/p-text-helpers";
|
|
||||||
|
|
||||||
export function getTenseVerbForm(conjR: T.VerbConjugation, tense: VerbTense | PerfectTense, tenseCategory: "basic" | "modal" | "perfect", voice: "active" | "passive"): T.VerbForm {
|
|
||||||
const conj = (voice === "passive" && conjR.passive) ? conjR.passive : conjR;
|
|
||||||
if (tenseCategory === "basic") {
|
|
||||||
if (tense === "presentVerb") {
|
|
||||||
return conj.imperfective.nonImperative;
|
|
||||||
}
|
|
||||||
if (tense === "subjunctiveVerb") {
|
|
||||||
return conj.perfective.nonImperative;
|
|
||||||
}
|
|
||||||
if (tense === "imperfectiveFuture") {
|
|
||||||
return conj.imperfective.future;
|
|
||||||
}
|
|
||||||
if (tense === "perfectiveFuture") {
|
|
||||||
return conj.perfective.future;
|
|
||||||
}
|
|
||||||
if (tense === "imperfectivePast") {
|
|
||||||
return conj.imperfective.past;
|
|
||||||
}
|
|
||||||
if (tense === "perfectivePast") {
|
|
||||||
return conj.perfective.past;
|
|
||||||
}
|
|
||||||
if (tense === "habitualImperfectivePast") {
|
|
||||||
return conj.imperfective.habitualPast;
|
|
||||||
}
|
|
||||||
if (tense === "habitualPerfectivePast") {
|
|
||||||
return conj.perfective.habitualPast;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tenseCategory === "modal") {
|
|
||||||
if (tense === "presentVerb") {
|
|
||||||
return conj.imperfective.modal.nonImperative;
|
|
||||||
}
|
|
||||||
if (tense === "subjunctiveVerb") {
|
|
||||||
return conj.perfective.modal.nonImperative;
|
|
||||||
}
|
|
||||||
if (tense === "imperfectiveFuture") {
|
|
||||||
return conj.imperfective.modal.future;
|
|
||||||
}
|
|
||||||
if (tense === "perfectiveFuture") {
|
|
||||||
return conj.perfective.modal.future;
|
|
||||||
}
|
|
||||||
if (tense === "imperfectivePast") {
|
|
||||||
return conj.imperfective.modal.past;
|
|
||||||
}
|
|
||||||
if (tense === "perfectivePast") {
|
|
||||||
return conj.perfective.modal.past;
|
|
||||||
}
|
|
||||||
if (tense === "habitualImperfectivePast") {
|
|
||||||
return conj.imperfective.modal.habitualPast;
|
|
||||||
}
|
|
||||||
if (tense === "habitualPerfectivePast") {
|
|
||||||
return conj.perfective.modal.habitualPast;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tense === "present perfect") {
|
|
||||||
return conj.perfect.present;
|
|
||||||
}
|
|
||||||
if (tense === "past perfect") {
|
|
||||||
return conj.perfect.past;
|
|
||||||
}
|
|
||||||
if (tense === "future perfect") {
|
|
||||||
return conj.perfect.future;
|
|
||||||
}
|
|
||||||
if (tense === "habitual perfect") {
|
|
||||||
return conj.perfect.habitual;
|
|
||||||
}
|
|
||||||
if (tense === "subjunctive perfect") {
|
|
||||||
return conj.perfect.subjunctive;
|
|
||||||
}
|
|
||||||
if (tense === "wouldBe perfect") {
|
|
||||||
return conj.perfect.affirmational;
|
|
||||||
}
|
|
||||||
if (tense === "pastSubjunctive perfect") {
|
|
||||||
return conj.perfect.pastSubjunctiveHypothetical;
|
|
||||||
}
|
|
||||||
throw new Error("unknown tense");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function getPersonFromNP(np: NPSelection): T.Person;
|
|
||||||
export function getPersonFromNP(np: NPSelection | ObjectNP): T.Person | undefined;
|
|
||||||
export function getPersonFromNP(np: NPSelection | ObjectNP): T.Person | undefined {
|
|
||||||
if (np === "none") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (typeof np === "number") return np;
|
|
||||||
if (np.type === "participle") {
|
|
||||||
return T.Person.ThirdPlurMale;
|
|
||||||
}
|
|
||||||
if (np.type === "pronoun") {
|
|
||||||
return np.person;
|
|
||||||
}
|
|
||||||
return np.number === "plural"
|
|
||||||
? (np.gender === "masc" ? T.Person.ThirdPlurMale : T.Person.ThirdPlurFemale)
|
|
||||||
: (np.gender === "masc" ? T.Person.ThirdSingMale : T.Person.ThirdSingFemale);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeBa(ps: T.PsString): T.PsString {
|
|
||||||
return psRemove(ps, concatPsString(grammarUnits.baParticle, " "));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isEquativeTense(t: VerbTense | EquativeTense | PerfectTense): t is EquativeTense {
|
|
||||||
return (t === "present" || t === "future" || t === "habitual" || t === "past" || t === "wouldBe" || t === "subjunctive" || t === "pastSubjunctive");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isPerfectTense(t: VerbTense | EquativeTense | PerfectTense): t is PerfectTense {
|
|
||||||
return (
|
|
||||||
t === "present perfect" ||
|
|
||||||
t === "habitual perfect" ||
|
|
||||||
t === "future perfect" ||
|
|
||||||
t === "past perfect" ||
|
|
||||||
t === "wouldBe perfect" ||
|
|
||||||
t === "subjunctive perfect" ||
|
|
||||||
t === "pastSubjunctive perfect"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isPastTense(tense: VerbTense | PerfectTense): boolean {
|
|
||||||
if (isPerfectTense(tense)) return true;
|
|
||||||
return tense.toLowerCase().includes("past");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeDuplicates(psv: T.PsString[]): T.PsString[] {
|
|
||||||
return psv.filter((ps, i, arr) => (
|
|
||||||
i === arr.findIndex(t => (
|
|
||||||
psStringEquals(t, ps)
|
|
||||||
))
|
|
||||||
));
|
|
||||||
}
|
|
|
@ -1,6 +1,9 @@
|
||||||
import shuffle from "./shuffle-array";
|
import shuffle from "./shuffle-array";
|
||||||
|
import {
|
||||||
|
Types as T,
|
||||||
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
|
||||||
export const startingWord = (words: Readonly<(NounEntry | AdjectiveEntry)[]>, p: string) => {
|
export const startingWord = (words: Readonly<(T.NounEntry | T.AdjectiveEntry)[]>, p: string) => {
|
||||||
const firstWord = words.find(w => w.p === p);
|
const firstWord = words.find(w => w.p === p);
|
||||||
return [
|
return [
|
||||||
...firstWord ? [firstWord] : [],
|
...firstWord ? [firstWord] : [],
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import {
|
|
||||||
Types as T,
|
|
||||||
removeFVarients,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
|
|
||||||
export function firstVariation(s: string): string {
|
|
||||||
return s.split(/[,|;]/)[0].trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function psStringFromEntry(entry: T.PsString): T.PsString {
|
|
||||||
return {
|
|
||||||
p: entry.p,
|
|
||||||
f: removeFVarients(entry.f),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLong<U>(x: T.SingleOrLengthOpts<U>): U {
|
|
||||||
if ("long" in x) {
|
|
||||||
return x.long;
|
|
||||||
}
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function capitalizeFirstLetter(string: string) {
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
||||||
}
|
|
|
@ -1,156 +0,0 @@
|
||||||
import {
|
|
||||||
pashtoConsonants,
|
|
||||||
endsWith,
|
|
||||||
countSyllables,
|
|
||||||
Types as T,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
|
||||||
|
|
||||||
export function isNounEntry(e: Entry): e is NounEntry {
|
|
||||||
if ("entry" in e) return false;
|
|
||||||
return !!(e.c && (e.c.includes("n. m.") || e.c.includes("n. f.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAdjectiveEntry(e: Entry): e is AdjectiveEntry {
|
|
||||||
if ("entry" in e) return false;
|
|
||||||
return !!e.c?.includes("adj.") && !isNounEntry(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAdverbEntry(e: Entry): e is AdverbEntry {
|
|
||||||
if ("entry" in e) return false;
|
|
||||||
return !!e.c?.includes("adv.");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isLocativeAdverbEntry(e: Entry): e is LocativeAdverbEntry {
|
|
||||||
return isAdverbEntry(e) && e.c.includes("loc. adv.");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isNounOrAdjEntry(e: Entry): e is (NounEntry | AdjectiveEntry) {
|
|
||||||
return isNounEntry(e) || isAdjectiveEntry(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isVerbEntry(e: Entry | T.DictionaryEntry): e is VerbEntry {
|
|
||||||
return "entry" in e && !!e.entry.c?.startsWith("v.");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isMascNounEntry(e: NounEntry | AdjectiveEntry): e is MascNounEntry {
|
|
||||||
return !!e.c && e.c.includes("n. m.");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFemNounEntry(e: NounEntry | AdjectiveEntry): e is FemNounEntry {
|
|
||||||
return !!e.c && e.c.includes("n. f.");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isUnisexNounEntry(e: NounEntry | AdjectiveEntry): e is UnisexNounEntry {
|
|
||||||
return isNounEntry(e) && e.c.includes("unisex");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAdjOrUnisexNounEntry(e: Entry): e is (AdjectiveEntry | UnisexNounEntry) {
|
|
||||||
return isAdjectiveEntry(e) || (
|
|
||||||
isNounEntry(e) && isUnisexNounEntry(e)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* shows if a noun/adjective has the basic (consonant / ه) inflection pattern
|
|
||||||
*
|
|
||||||
* @param e
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function isPattern1Entry<T extends (NounEntry | AdjectiveEntry)>(e: T): e is Pattern1Entry<T> {
|
|
||||||
if (e.noInf) return false;
|
|
||||||
if (e.infap) return false;
|
|
||||||
if (isFemNounEntry(e)) {
|
|
||||||
return (
|
|
||||||
endsWith([{ p: "ه", f: "a" }, { p: "ح", f: "a" }], e) ||
|
|
||||||
(endsWith({ p: pashtoConsonants }, e) && !e.c.includes("anim."))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
endsWith([{ p: pashtoConsonants }], e) ||
|
|
||||||
endsWith([{ p: "ه", f: "u" }, { p: "ه", f: "h" }], e) ||
|
|
||||||
endsWith([{ p: "ای", f: "aay" }, { p: "وی", f: "ooy" }], e)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* shows if a noun/adjective has the unstressed ی inflection pattern
|
|
||||||
*
|
|
||||||
* @param e
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function isPattern2Entry<T extends (NounEntry | AdjectiveEntry)>(e: T): e is Pattern2Entry<T> {
|
|
||||||
if (e.noInf) return false;
|
|
||||||
if (e.infap) return false;
|
|
||||||
if (isFemNounEntry(e)) {
|
|
||||||
return !e.c.includes("pl.") && endsWith({ p: "ې", f: "e" }, e, true);
|
|
||||||
}
|
|
||||||
// TODO: check if it's a single syllable word, in which case it would be pattern 1
|
|
||||||
return endsWith({ p: "ی", f: "ey" }, e, true) && (countSyllables(e.f) > 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* shows if a noun/adjective has the stressed ی inflection pattern
|
|
||||||
*
|
|
||||||
* @param e
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function isPattern3Entry<T extends (NounEntry | AdjectiveEntry)>(e: T): e is Pattern3Entry<T> {
|
|
||||||
if (e.noInf) return false;
|
|
||||||
if (e.infap) return false;
|
|
||||||
if (isFemNounEntry(e)) {
|
|
||||||
return endsWith({ p: "ۍ" }, e);
|
|
||||||
}
|
|
||||||
return (countSyllables(e.f) > 1)
|
|
||||||
? endsWith({ p: "ی", f: "éy" }, e, true)
|
|
||||||
: endsWith({ p: "ی", f: "ey" }, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* shows if a noun/adjective has the "Pashtoon" inflection pattern
|
|
||||||
*
|
|
||||||
* @param e
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function isPattern4Entry<T extends (NounEntry | AdjectiveEntry)>(e: T): e is Pattern4Entry<T> {
|
|
||||||
if (e.noInf) return false;
|
|
||||||
return (
|
|
||||||
!!(e.infap && e.infaf && e.infbp && e.infbf)
|
|
||||||
&&
|
|
||||||
(e.infap.slice(1).includes("ا") && e.infap.slice(-1) === "ه")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* shows if a noun/adjective has the shorter squish inflection pattern
|
|
||||||
*
|
|
||||||
* @param e
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function isPattern5Entry<T extends (NounEntry | AdjectiveEntry)>(e: T): e is Pattern5Entry<T> {
|
|
||||||
if (e.noInf) return false;
|
|
||||||
return (
|
|
||||||
!!(e.infap && e.infaf && e.infbp && e.infbf)
|
|
||||||
&&
|
|
||||||
(e.infaf.slice(-1) === "u")
|
|
||||||
&&
|
|
||||||
!e.infap.slice(1).includes("ا")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isPattern6FemEntry(e: FemNounEntry): e is Pattern6FemEntry<FemNounEntry> {
|
|
||||||
if (!isFemNounEntry(e)) return false;
|
|
||||||
if (e.c.includes("anim.")) return false;
|
|
||||||
return e.p.slice(-1) === "ي";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isPluralNounEntry<U extends NounEntry>(e: U): e is PluralNounEntry<U> {
|
|
||||||
return e.c.includes("pl.");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSingularEntry<U extends NounEntry>(e: U): e is SingularEntry<U> {
|
|
||||||
return !isPluralNounEntry(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isArrayOneOrMore<U>(a: U[]): a is T.ArrayOneOrMore<U> {
|
|
||||||
return a.length > 0;
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
type NounEntry = import("@lingdocs/pashto-inflector").Types.DictionaryEntry & { c: string } & { __brand: "a noun entry" };
|
|
||||||
type MascNounEntry = NounEntry & { __brand2: "a masc noun entry" };
|
|
||||||
type FemNounEntry = NounEntry & { __brand2: "a fem noun entry" };
|
|
||||||
type UnisexNounEntry = MascNounEntry & { __brand3: "a unisex noun entry" };
|
|
||||||
type AdverbEntry = import("@lingdocs/pashto-inflector").Types.DictionaryEntry & { c: string } & { __brand: "an adverb entry" };
|
|
||||||
type LocativeAdverbEntry = AdverbEntry & { __brand2: "a locative adverb entry" };
|
|
||||||
type AdjectiveEntry = import("@lingdocs/pashto-inflector").Types.DictionaryEntry & { c: string } & { __brand: "an adjective entry" };
|
|
||||||
type VerbEntry = {
|
|
||||||
entry: import("@lingdocs/pashto-inflector").Types.DictionaryEntry & { __brand: "a verb entry" },
|
|
||||||
// TODO: the compliment could also be typed? Maybe?
|
|
||||||
complement?: import("@lingdocs/pashto-inflector").Types.DictionaryEntry,
|
|
||||||
};
|
|
||||||
|
|
||||||
type SingularEntry<T extends NounEntry> = T & { __brand7: "a singular noun - as opposed to an always plural noun" };
|
|
||||||
type PluralNounEntry<T extends NounEntry> = T & { __brand7: "a noun that is always plural" };
|
|
||||||
|
|
||||||
type Pattern1Entry<T> = T & { __brand3: "basic inflection pattern" };
|
|
||||||
type Pattern2Entry<T> = T & { __brand3: "ending in unstressed ی pattern" };
|
|
||||||
type Pattern3Entry<T> = T & { __brand3: "ending in stressed ی pattern" };
|
|
||||||
type Pattern4Entry<T> = T & { __brand3: "Pashtoon pattern" };
|
|
||||||
type Pattern5Entry<T> = T & { __brand3: "short squish pattern" };
|
|
||||||
type Pattern6FemEntry<T extends FemNounEntry> = T & { __brand3: "non anim. ending in ي" };
|
|
||||||
type NonInflecting<T> = T & { __brand3: "non-inflecting" };
|
|
||||||
|
|
||||||
type Entry = NounEntry | AdjectiveEntry | AdverbEntry | VerbEntry;
|
|
||||||
|
|
||||||
type Words = {
|
|
||||||
nouns: NounEntry[],
|
|
||||||
adjectives: AdjectiveEntry[],
|
|
||||||
verbs: VerbEntry[],
|
|
||||||
adverbs: AdverbEntry[],
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
// type MascSingNounEntry = ""; // can change number only
|
|
||||||
// type FemSingNounEntry = ""; // can change nuber only
|
|
||||||
// type MascPlurNounEntry = ""; // can change nothing
|
|
||||||
// type FemPlurNounEntry = ""; // can change nothing
|
|
||||||
// type UnisexNounEntry = ""; // can change number or gender
|
|
||||||
|
|
||||||
|
|
||||||
type VPSelection = {
|
|
||||||
type: "VPSelection",
|
|
||||||
subject: NPSelection,
|
|
||||||
object: Exclude<VerbObject, undefined>,
|
|
||||||
verb: Exclude<VerbSelection, "object">,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: make this Rendered<VPSelection> with recursive Rendered<>
|
|
||||||
type VPRendered = {
|
|
||||||
type: "VPRendered",
|
|
||||||
king: "subject" | "object",
|
|
||||||
servant: "subject" | "object" | undefined,
|
|
||||||
isPast: boolean,
|
|
||||||
isTransitive: boolean,
|
|
||||||
isCompound: "stative" | "dynamic" | false,
|
|
||||||
subject: Rendered<NPSelection>,
|
|
||||||
object: Rendered<NPSelection> | ObjectNP,
|
|
||||||
verb: VerbRendered,
|
|
||||||
englishBase?: string[],
|
|
||||||
}
|
|
||||||
|
|
||||||
type VerbTense = "presentVerb"
|
|
||||||
| "subjunctiveVerb"
|
|
||||||
| "perfectiveFuture"
|
|
||||||
| "imperfectiveFuture"
|
|
||||||
| "perfectivePast"
|
|
||||||
| "imperfectivePast"
|
|
||||||
| "habitualPerfectivePast"
|
|
||||||
| "habitualImperfectivePast";
|
|
||||||
|
|
||||||
type PerfectTense = `${EquativeTense} perfect`;
|
|
||||||
|
|
||||||
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: import("@lingdocs/pashto-inflector").Types.Transitivity,
|
|
||||||
isCompound: "stative" | "dynamic" | false,
|
|
||||||
voice: "active" | "passive",
|
|
||||||
changeTransitivity?: (t: "transitive" | "grammatically transitive") => VerbSelection,
|
|
||||||
changeStatDyn?: (t: "stative" | "dynamic") => VerbSelection,
|
|
||||||
changeVoice?: (v: "active" | "passive", subj?: NPSelection) => VerbSelection,
|
|
||||||
// TODO: changeStativeDynamic
|
|
||||||
// TODO: add in aspect element here??
|
|
||||||
negative: boolean,
|
|
||||||
} & ({
|
|
||||||
tense: VerbTense,
|
|
||||||
tenseCategory: "basic" | "modal",
|
|
||||||
} | {
|
|
||||||
tense: PerfectTense,
|
|
||||||
tenseCategory: "perfect"
|
|
||||||
});
|
|
||||||
|
|
||||||
type VerbRendered = Omit<VerbSelection, "object"> & {
|
|
||||||
ps: {
|
|
||||||
head: import("@lingdocs/pashto-inflector").Types.PsString | undefined,
|
|
||||||
rest: import("@lingdocs/pashto-inflector").Types.SingleOrLengthOpts<
|
|
||||||
import("@lingdocs/pashto-inflector").Types.PsString[]
|
|
||||||
>,
|
|
||||||
},
|
|
||||||
hasBa: boolean,
|
|
||||||
person: import("@lingdocs/pashto-inflector").Types.Person,
|
|
||||||
};
|
|
||||||
|
|
||||||
type VerbObject =
|
|
||||||
// transitive verb - object not selected yet
|
|
||||||
undefined |
|
|
||||||
// transitive verb - obect selected
|
|
||||||
NPSelection |
|
|
||||||
// grammatically transitive verb with unspoken 3rd pers masc plur entity
|
|
||||||
// or intransitive "none"
|
|
||||||
ObjectNP;
|
|
||||||
|
|
||||||
type NPSelection = NounSelection | PronounSelection | ParticipleSelection;
|
|
||||||
|
|
||||||
type NPType = "noun" | "pronoun" | "participle";
|
|
||||||
|
|
||||||
type ObjectNP = "none" | import("@lingdocs/pashto-inflector").Types.Person.ThirdPlurMale;
|
|
||||||
|
|
||||||
// TODO require/import Person and PsString
|
|
||||||
type NounSelection = {
|
|
||||||
type: "noun",
|
|
||||||
entry: NounEntry,
|
|
||||||
gender: import("@lingdocs/pashto-inflector").Types.Gender,
|
|
||||||
number: NounNumber,
|
|
||||||
dynamicComplement?: boolean,
|
|
||||||
// TODO: Implement
|
|
||||||
// adjectives: [],
|
|
||||||
// TODO: Implement
|
|
||||||
// possesor: NPSelection | undefined,
|
|
||||||
/* method only present if it's possible to change gender */
|
|
||||||
changeGender?: (gender: import("@lingdocs/pashto-inflector").Types.Gender) => NounSelection,
|
|
||||||
/* method only present if it's possible to change number */
|
|
||||||
changeNumber?: (number: NounNumber) => NounSelection,
|
|
||||||
};
|
|
||||||
|
|
||||||
// take an argument for subject/object in rendering English
|
|
||||||
type PronounSelection = {
|
|
||||||
type: "pronoun",
|
|
||||||
person: import("@lingdocs/pashto-inflector").Types.Person,
|
|
||||||
distance: "near" | "far",
|
|
||||||
};
|
|
||||||
|
|
||||||
type ParticipleSelection = {
|
|
||||||
type: "participle",
|
|
||||||
verb: VerbEntry,
|
|
||||||
};
|
|
||||||
|
|
||||||
// not object
|
|
||||||
// type Primitive = string | Function | number | boolean | Symbol | undefined | null;
|
|
||||||
// If T has key K ("user"), replace it
|
|
||||||
type ReplaceKey<T, K extends string, R> = T extends Record<K, unknown> ? (Omit<T, K> & Record<K, R>) : T;
|
|
||||||
|
|
||||||
type FormVersion = { removeKing: boolean, shrinkServant: boolean };
|
|
||||||
|
|
||||||
type Rendered<T extends NPSelection> = ReplaceKey<
|
|
||||||
Omit<T, "changeGender" | "changeNumber" | "changeDistance">,
|
|
||||||
"e",
|
|
||||||
string
|
|
||||||
> & {
|
|
||||||
ps: import("@lingdocs/pashto-inflector").Types.PsString[],
|
|
||||||
e?: string,
|
|
||||||
inflected: boolean,
|
|
||||||
person: T.Person,
|
|
||||||
};
|
|
||||||
// TODO: recursive changing this down into the possesor etc.
|
|
||||||
|
|
||||||
|
|
||||||
// TPSelection => TPRendered => TPCompiled
|
|
||||||
// NPSelection => NPRendered => NPCompiled
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// TODO: I think this is just used for the old equative explorer
|
||||||
|
// build a new better equative explorer using NP Pickers etc and deprecate all this
|
||||||
|
|
||||||
type EquativeTense = "present" | "subjunctive" | "habitual" | "past" | "future" | "wouldBe" | "pastSubjunctive";
|
type EquativeTense = "present" | "subjunctive" | "habitual" | "past" | "future" | "wouldBe" | "pastSubjunctive";
|
||||||
type NounNumber = "singular" | "plural";
|
type NounNumber = "singular" | "plural";
|
||||||
|
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* replacement from the React useState hook that will persist the state in local storage
|
|
||||||
*
|
|
||||||
* @param defaultValue The default value to use if there was nothing saved previously OR
|
|
||||||
* a function that will take the saved value and return a modified new value to start with
|
|
||||||
* @param key a key for saving the state in locolStorage
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export default function useStickyState<T extends string | object | boolean | undefined | null>(
|
|
||||||
defaultValue: T | ((old: T | undefined) => T),
|
|
||||||
key: string
|
|
||||||
): [
|
|
||||||
value: T,
|
|
||||||
setValue: React.Dispatch<React.SetStateAction<T>>,
|
|
||||||
] {
|
|
||||||
const [value, setValue] = useState<T>(() => {
|
|
||||||
const v = window.localStorage.getItem(key);
|
|
||||||
// nothing saved
|
|
||||||
if (v === null) {
|
|
||||||
if (typeof defaultValue === "function") {
|
|
||||||
return defaultValue(undefined);
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
// something saved before
|
|
||||||
try {
|
|
||||||
const old = JSON.parse(v) as T;
|
|
||||||
if (typeof defaultValue === "function") {
|
|
||||||
return defaultValue(old);
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("error parsting saved state from stickState");
|
|
||||||
return (typeof defaultValue === "function")
|
|
||||||
? defaultValue(undefined)
|
|
||||||
: defaultValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(value));
|
|
||||||
}, [key, value]);
|
|
||||||
|
|
||||||
return [value, setValue];
|
|
||||||
}
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React, { createContext, useEffect } from "react"
|
import React, { createContext, useEffect } from "react"
|
||||||
import useStickyState from "./useStickyState";
|
|
||||||
import {
|
import {
|
||||||
AT,
|
AT,
|
||||||
getUser,
|
getUser,
|
||||||
userObjIsEqual,
|
userObjIsEqual,
|
||||||
} from "@lingdocs/lingdocs-main";
|
} from "@lingdocs/lingdocs-main";
|
||||||
|
import {
|
||||||
|
useStickyState,
|
||||||
|
} from "@lingdocs/pashto-inflector";
|
||||||
import { CronJob } from "cron";
|
import { CronJob } from "cron";
|
||||||
import { postSavedResults } from "./lib/game-results";
|
import { postSavedResults } from "./lib/game-results";
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import rawWords from "./raw-words";
|
import rawWords from "./raw-words";
|
||||||
import {
|
import {
|
||||||
isAdjectiveEntry,
|
typePredicates as tp,
|
||||||
isNounEntry,
|
Types as T,
|
||||||
isVerbEntry,
|
} from "@lingdocs/pashto-inflector";
|
||||||
isAdverbEntry,
|
|
||||||
} from "../lib/type-predicates";
|
|
||||||
import { categorize } from "../lib/categorize";
|
import { categorize } from "../lib/categorize";
|
||||||
|
|
||||||
const words = categorize<Entry, Words>(rawWords, {
|
const words = categorize<T.Entry, T.Words>(rawWords, {
|
||||||
"nouns": isNounEntry,
|
"nouns": tp.isNounEntry,
|
||||||
"adjectives": isAdjectiveEntry,
|
"adjectives": tp.isAdjectiveEntry,
|
||||||
"verbs": isVerbEntry,
|
"verbs": tp.isVerbEntry,
|
||||||
"adverbs": isAdverbEntry,
|
"adverbs": tp.isAdverbEntry,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default words;
|
export default words;
|
||||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -1684,14 +1684,15 @@
|
||||||
pbf "^3.2.1"
|
pbf "^3.2.1"
|
||||||
rambda "^6.7.0"
|
rambda "^6.7.0"
|
||||||
|
|
||||||
"@lingdocs/pashto-inflector@^1.7.0":
|
"@lingdocs/pashto-inflector@^1.8.0":
|
||||||
version "1.7.0"
|
version "1.8.0"
|
||||||
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.7.0.tgz#214b0be0528d3c4017db78255c61b83417d20c35"
|
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.8.0.tgz#51d4d55cc9a242feb29556136096a4bcfb5474f1"
|
||||||
integrity sha512-/7CrJx1KGO4xPJOkWG9LLEteEdeQ20MA5oX4AI10uQuaZpYULR5oF1J02mwS21wERoMUYjCwMrcZ+G71w/T34A==
|
integrity sha512-EE2F9mEs6px6jpolz75eHoztC4EDJ3090AhKKDaTvun3O5pRSu69yGJJpyAc1sj1SphbQeZSmTdwNAiR43RMBg==
|
||||||
dependencies:
|
dependencies:
|
||||||
classnames "^2.2.6"
|
classnames "^2.2.6"
|
||||||
pbf "^3.2.1"
|
pbf "^3.2.1"
|
||||||
rambda "^6.7.0"
|
rambda "^6.7.0"
|
||||||
|
react-select "^5.2.2"
|
||||||
|
|
||||||
"@mdx-js/mdx@^0.15.5":
|
"@mdx-js/mdx@^0.15.5":
|
||||||
version "0.15.7"
|
version "0.15.7"
|
||||||
|
@ -10486,6 +10487,19 @@ react-select@^5.1.0:
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
react-transition-group "^4.3.0"
|
react-transition-group "^4.3.0"
|
||||||
|
|
||||||
|
react-select@^5.2.2:
|
||||||
|
version "5.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.2.tgz#3d5edf0a60f1276fd5f29f9f90a305f0a25a5189"
|
||||||
|
integrity sha512-miGS2rT1XbFNjduMZT+V73xbJEeMzVkJOz727F6MeAr2hKE0uUSA8Ff7vD44H32x2PD3SRB6OXTY/L+fTV3z9w==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.0"
|
||||||
|
"@emotion/cache" "^11.4.0"
|
||||||
|
"@emotion/react" "^11.1.1"
|
||||||
|
"@types/react-transition-group" "^4.4.0"
|
||||||
|
memoize-one "^5.0.0"
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
react-transition-group "^4.3.0"
|
||||||
|
|
||||||
react-smooth-collapse@^2.1.0:
|
react-smooth-collapse@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-smooth-collapse/-/react-smooth-collapse-2.1.0.tgz#64be7af94b61d6d203d9a09af0fba671f9b5983c"
|
resolved "https://registry.yarnpkg.com/react-smooth-collapse/-/react-smooth-collapse-2.1.0.tgz#64be7af94b61d6d203d9a09af0fba671f9b5983c"
|
||||||
|
|
Loading…
Reference in New Issue