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": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@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/react": "^11.1.0",
|
||||
"@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,
|
||||
defaultTextOptions as opts,
|
||||
getEnglishWord,
|
||||
Types as T,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
|
||||
function InflectionCarousel({ items }: { items: (NounEntry | AdjectiveEntry)[] }) {
|
||||
function InflectionCarousel({ items }: { items: (T.NounEntry | T.AdjectiveEntry)[] }) {
|
||||
if (!items.length) {
|
||||
return "no items for carousel";
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// TODO: DEPRECATE THIS AND USE THE PASHTO INFLECTER NP PRONOUN PICKER!
|
||||
|
||||
import {
|
||||
Types as T,
|
||||
ButtonSelect,
|
||||
useStickyState,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import useStickyState from "../../useStickyState";
|
||||
|
||||
const gColors = {
|
||||
masc: "LightSkyBlue",
|
||||
|
@ -143,4 +145,3 @@ function PronounPicker({ onChange, pronoun, isObject }: {
|
|||
};
|
||||
|
||||
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,
|
||||
personGender,
|
||||
personIsPlural,
|
||||
typePredicates,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import {
|
||||
ExplorerState,
|
||||
|
@ -14,13 +15,13 @@ import {
|
|||
equativeMachine,
|
||||
assembleEquativeOutput,
|
||||
} from "../../lib/equative-machine";
|
||||
import {
|
||||
const {
|
||||
isUnisexNounEntry,
|
||||
isAdjectiveEntry,
|
||||
isVerbEntry,
|
||||
isLocativeAdverbEntry,
|
||||
isNounEntry,
|
||||
} from "../../lib/type-predicates";
|
||||
} = typePredicates;
|
||||
|
||||
function chooseLength<O>(o: T.SingleOrLengthOpts<O>, length: "short" | "long"): 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 {
|
||||
type: "compliment",
|
||||
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)) {
|
||||
return {
|
||||
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 (!length && "long" in assembleEquativeOutput(equativeMachine({
|
||||
subject: { type: "pronoun", pronounType: "near", person: 0 },
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { nouns, adjectives, verbs, adverbs } from "../../words/words";
|
||||
import {
|
||||
isLocativeAdverbEntry,
|
||||
isUnisexNounEntry,
|
||||
} from "../../lib/type-predicates";
|
||||
Types as T,
|
||||
typePredicates as tp,
|
||||
} 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]) {
|
||||
return [...arr].sort((a, b) => (
|
||||
// @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 nonUnisexNouns = sort(nouns.filter(x => !isUnisexNounEntry(x)) as (MascNounEntry | FemNounEntry)[]);
|
||||
const unisexNouns = sort(nouns.filter(x => tp.isUnisexNounEntry(x)) as T.UnisexNounEntry[]);
|
||||
const nonUnisexNouns = sort(nouns.filter(x => !tp.isUnisexNounEntry(x)) as (T.MascNounEntry | T.FemNounEntry)[]);
|
||||
|
||||
const inputs = {
|
||||
adjective: sort(adjectives),
|
||||
unisexNoun: unisexNouns,
|
||||
noun: nonUnisexNouns,
|
||||
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];
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import inputs from "./explorer-inputs";
|
||||
import { ExplorerState, ExplorerReducerAction } from "./explorer-types";
|
||||
import {
|
||||
Types as T,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
|
||||
export function reducer(state: ExplorerState, action: ExplorerReducerAction): ExplorerState {
|
||||
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]);
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -14,9 +14,17 @@ import {
|
|||
getEnglishWord,
|
||||
Types as T,
|
||||
removeFVarients,
|
||||
typePredicates,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { isAdjectiveEntry, isAdverbEntry, isFemNounEntry, isMascNounEntry, isNounEntry, isPluralNounEntry } from "../../lib/type-predicates";
|
||||
import Select from "react-select";
|
||||
const {
|
||||
isAdjectiveEntry,
|
||||
isAdverbEntry,
|
||||
isFemNounEntry,
|
||||
isMascNounEntry,
|
||||
isNounEntry,
|
||||
isPluralNounEntry,
|
||||
} = typePredicates;
|
||||
|
||||
const zIndexProps = {
|
||||
menuPortalTarget: document.body,
|
||||
|
@ -83,7 +91,7 @@ export function InputSelector({ state, dispatch, entity }: {
|
|||
}
|
||||
|
||||
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
|
||||
// @ts-ignore
|
||||
: state[entity][type];
|
||||
|
@ -164,7 +172,7 @@ function GenderAndNumberSelect({ state, dispatch, entity }: {
|
|||
</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;
|
||||
// TODO: THIS IS SUUUPER SKETCH
|
||||
const eng = (isNounEntry(e) || isAdjectiveEntry(e) || isAdverbEntry(e))
|
||||
|
|
|
@ -17,14 +17,14 @@ export type SubjectEntityInfo = EntitiyInfo & { type: SubjectType };
|
|||
|
||||
export type PredicateEntityInfo = EntitiyInfo & {
|
||||
type: PredicateType,
|
||||
adjective: AdjectiveEntry,
|
||||
adverb: LocativeAdverbEntry,
|
||||
adjective: import("@lingdocs/pashto-inflector").Types.AdjectiveEntry,
|
||||
adverb: import("@lingdocs/pashto-inflector").Types.LocativeAdverbEntry,
|
||||
}
|
||||
|
||||
type EntitiyInfo = {
|
||||
noun: NounEntry,
|
||||
participle: VerbEntry,
|
||||
unisexNoun: UnisexNounEntry,
|
||||
noun: import("@lingdocs/pashto-inflector").Types.NounEntry,
|
||||
participle: import("@lingdocs/pashto-inflector").Types.VerbEntry,
|
||||
unisexNoun: import("@lingdocs/pashto-inflector").Types.UnisexNounEntry,
|
||||
info: {
|
||||
number: NounNumber,
|
||||
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,
|
||||
InflectionsTable,
|
||||
inflectWord,
|
||||
typePredicates as tp,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import shuffle from "../../lib/shuffle-array";
|
||||
import InflectionCarousel from "../../components/InflectionCarousel";
|
||||
|
@ -29,12 +30,8 @@ import { nouns } from "../../words/words";
|
|||
import Link from "../../components/Link";
|
||||
import Table from "../../components/Table";
|
||||
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).
|
||||
|
||||
|
@ -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" }} />
|
||||
|
||||
<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.)
|
||||
|
|
|
@ -21,24 +21,15 @@ import {
|
|||
grammarUnits,
|
||||
InflectionsTable,
|
||||
inflectWord,
|
||||
typePredicates as tp,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import {
|
||||
isNounOrVerb,
|
||||
isPattern1Entry,
|
||||
isPattern2Entry,
|
||||
isPattern3Entry,
|
||||
isPattern4Entry,
|
||||
isPattern5Entry,
|
||||
isUnisexNounEntry,
|
||||
} from "../../lib/type-predicates";
|
||||
import InflectionCarousel from "../../components/InflectionCarousel";
|
||||
import { nouns, adjectives } from "../../words/words";
|
||||
import { startingWord } from "../../lib/starting-word";
|
||||
import Link from "../../components/Link";
|
||||
import psmd from "../../lib/psmd";
|
||||
|
||||
export const words = [
|
||||
...nouns.filter(isUnisexNounEntry),
|
||||
...nouns.filter(tp.isUnisexNounEntry),
|
||||
...adjectives,
|
||||
];
|
||||
|
||||
|
@ -58,7 +49,7 @@ These words always end in:
|
|||
- **Feminine:** - <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} />
|
||||
|
||||
<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" }} />
|
||||
|
||||
<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.
|
||||
|
||||
<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
|
||||
|
||||
<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.
|
||||
|
||||
<InflectionCarousel items={startingWord(
|
||||
words.filter(isPattern5Entry),
|
||||
words.filter(tp.isPattern5Entry),
|
||||
"غل",
|
||||
)} />
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ import {
|
|||
defaultTextOptions as opts,
|
||||
InlinePs,
|
||||
Examples,
|
||||
firstVariation,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import genderColors from "../../lib/gender-colors";
|
||||
import { firstVariation } from "../../lib/text-tools";
|
||||
import GenderTable from "../../components/GenderTable";
|
||||
import Link from "../../components/Link";
|
||||
export const femColor = genderColors.f;
|
||||
|
|
|
@ -3,13 +3,13 @@ title: Phrase Builder
|
|||
fullWidth: true
|
||||
---
|
||||
|
||||
import PhraseBuilder from "../../components/phrase-builder/PhraseBuilder";
|
||||
import {
|
||||
VPExplorer,
|
||||
defaultTextOptions,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { nouns, verbs } from "../../words/words";
|
||||
|
||||
<PhraseBuilder
|
||||
<VPExplorer
|
||||
opts={defaultTextOptions}
|
||||
nouns={nouns}
|
||||
verbs={verbs}
|
||||
|
|
|
@ -2,18 +2,16 @@
|
|||
title: Pronoun Picker
|
||||
---
|
||||
|
||||
import PronounPicker from "../../components/np-picker/PronounPicker";
|
||||
import PronounPicker from "../../components/PronounPicker";
|
||||
import {
|
||||
defaultTextOptions as opts,
|
||||
InlinePs,
|
||||
ButtonSelect,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
randomPerson,
|
||||
randomSubjObj,
|
||||
isInvalidSubjObjCombo,
|
||||
} from "../../lib/np-tools";
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { useState } from "react";
|
||||
|
||||
export function RPicker() {
|
||||
// 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 verbs = verbsRaw.filter(v => !v.c?.includes("comp."));
|
||||
export const verbs = verbsRaw.filter(v => !("complement" in v));
|
||||
|
||||
export function InfoCarousel({ items, highlighted, hidePastParticiple }) {
|
||||
return (
|
||||
|
|
|
@ -12,44 +12,38 @@ import {
|
|||
pashtoConsonants,
|
||||
inflectWord,
|
||||
isUnisexSet,
|
||||
typePredicates as tp,
|
||||
firstVariation,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { nouns } from "../../words/words";
|
||||
import {
|
||||
firstVariation,
|
||||
} from "../../lib/text-tools";
|
||||
import {
|
||||
isMascNounEntry,
|
||||
isFemNounEntry,
|
||||
isUnisexNounEntry,
|
||||
} from "../../lib/type-predicates";
|
||||
import { categorize } from "../../lib/categorize";
|
||||
|
||||
const genders: T.Gender[] = ["masc", "fem"];
|
||||
|
||||
const mascNouns = nouns.filter(isMascNounEntry);
|
||||
const mascNouns = nouns.filter(tp.isMascNounEntry);
|
||||
const femNouns = [
|
||||
...nouns.filter(isFemNounEntry),
|
||||
...getFemVersions(mascNouns.filter(isUnisexNounEntry)),
|
||||
...nouns.filter(tp.isFemNounEntry),
|
||||
...getFemVersions(mascNouns.filter(tp.isUnisexNounEntry)),
|
||||
];
|
||||
|
||||
const types = {
|
||||
masc: categorize<MascNounEntry, {
|
||||
consonantMasc: MascNounEntry[],
|
||||
eyMasc: MascNounEntry[],
|
||||
uMasc: MascNounEntry[],
|
||||
yMasc: MascNounEntry[],
|
||||
masc: categorize<T.MascNounEntry, {
|
||||
consonantMasc: T.MascNounEntry[],
|
||||
eyMasc: T.MascNounEntry[],
|
||||
uMasc: T.MascNounEntry[],
|
||||
yMasc: T.MascNounEntry[],
|
||||
}>(mascNouns, {
|
||||
consonantMasc: endsWith([{ p: pashtoConsonants }, { p: "و", f: "w" }]),
|
||||
eyMasc: endsWith({ p: "ی", f: "ey" }),
|
||||
uMasc: endsWith({ p: "ه", f: "u" }),
|
||||
yMasc: endsWith([{ p: "ای", f: "aay" }, { p: "وی", f: "ooy" }]),
|
||||
}),
|
||||
fem: categorize<FemNounEntry, {
|
||||
aaFem: FemNounEntry[],
|
||||
eeFem: FemNounEntry[],
|
||||
uyFem: FemNounEntry[],
|
||||
aFem: FemNounEntry[],
|
||||
eFem: FemNounEntry[],
|
||||
fem: categorize<T.FemNounEntry, {
|
||||
aaFem: T.FemNounEntry[],
|
||||
eeFem: T.FemNounEntry[],
|
||||
uyFem: T.FemNounEntry[],
|
||||
aFem: T.FemNounEntry[],
|
||||
eFem: T.FemNounEntry[],
|
||||
}>(femNouns, {
|
||||
aaFem: endsWith({ p: "ا", f: "aa" }),
|
||||
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) => {
|
||||
const infs = inflectWord(n);
|
||||
if (!infs || !infs.inflections) return undefined;
|
||||
|
@ -68,18 +62,18 @@ function getFemVersions(uns: UnisexNounEntry[]): FemNounEntry[] {
|
|||
e: n.e,
|
||||
...infs.inflections.fem[0][0],
|
||||
} as T.DictionaryEntry;
|
||||
}).filter(n => !!n) as FemNounEntry[];
|
||||
}).filter(n => !!n) as T.FemNounEntry[];
|
||||
}
|
||||
|
||||
function flatten<T>(o: Record<string, T[]>): T[] {
|
||||
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);
|
||||
}
|
||||
|
||||
type CategorySet = Record<string, NounEntry[]>;
|
||||
type CategorySet = Record<string, T.NounEntry[]>;
|
||||
// for some reason we need to use this CategorySet type here... 🤷♂️
|
||||
const exceptions: Record<string, CategorySet> = {
|
||||
masc: {
|
||||
|
|
|
@ -12,17 +12,13 @@ import {
|
|||
defaultTextOptions as opts,
|
||||
inflectWord,
|
||||
standardizePashto,
|
||||
firstVariation,
|
||||
typePredicates as tp,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { nouns } from "../../words/words";
|
||||
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";
|
||||
// TODO: make pattern types as overlay types
|
||||
const types = intoPatterns(unisexNouns);
|
||||
|
@ -41,7 +37,7 @@ export default function UnisexNounGame({ id, link }: { id: string, link: string
|
|||
do {
|
||||
type = getRandomFromList(keys);
|
||||
} while (!pool[type].length);
|
||||
const entry = getRandomFromList<UnisexNounEntry>(
|
||||
const entry = getRandomFromList<T.UnisexNounEntry>(
|
||||
// @ts-ignore
|
||||
pool[type]
|
||||
);
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import {
|
||||
typePredicates,
|
||||
// super weird, need to es-lint disable this this
|
||||
// eslint-disable-next-line
|
||||
Types as T,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
const {
|
||||
isPattern1Entry,
|
||||
isPattern2Entry,
|
||||
isPattern3Entry,
|
||||
isPattern4Entry,
|
||||
isPattern5Entry,
|
||||
} from "./type-predicates";
|
||||
} = typePredicates;
|
||||
|
||||
/**
|
||||
* 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 🤷♂️
|
||||
|
||||
export function intoPatterns<T extends (NounEntry | AdjectiveEntry)>(words: T[]): {
|
||||
"pattern1": Pattern1Entry<T>[],
|
||||
"pattern2": Pattern2Entry<T>[],
|
||||
"pattern3": Pattern3Entry<T>[],
|
||||
"pattern4": Pattern4Entry<T>[],
|
||||
"pattern5": Pattern5Entry<T>[],
|
||||
"other": NonInflecting<T>[],
|
||||
export function intoPatterns<T extends (T.NounEntry | T.AdjectiveEntry)>(words: T[]): {
|
||||
"pattern1": T.Pattern1Entry<T>[],
|
||||
"pattern2": T.Pattern2Entry<T>[],
|
||||
"pattern3": T.Pattern3Entry<T>[],
|
||||
"pattern4": T.Pattern4Entry<T>[],
|
||||
"pattern5": T.Pattern5Entry<T>[],
|
||||
"other": T.NonInflecting<T>[],
|
||||
// "pattern6fem": Pattern6FemNoun<T>[],
|
||||
} {
|
||||
return categorize<(NounEntry | AdjectiveEntry), {
|
||||
"pattern1": Pattern1Entry<T>[],
|
||||
"pattern2": Pattern2Entry<T>[],
|
||||
"pattern3": Pattern3Entry<T>[],
|
||||
"pattern4": Pattern4Entry<T>[],
|
||||
"pattern5": Pattern5Entry<T>[],
|
||||
"other": NonInflecting<T>[],
|
||||
return categorize<(T.NounEntry | T.AdjectiveEntry), {
|
||||
"pattern1": T.Pattern1Entry<T>[],
|
||||
"pattern2": T.Pattern2Entry<T>[],
|
||||
"pattern3": T.Pattern3Entry<T>[],
|
||||
"pattern4": T.Pattern4Entry<T>[],
|
||||
"pattern5": T.Pattern5Entry<T>[],
|
||||
"other": T.NonInflecting<T>[],
|
||||
// "pattern6fem": Pattern6FemNoun<T>[],
|
||||
}>(
|
||||
words,
|
||||
|
|
|
@ -5,11 +5,9 @@ import {
|
|||
isUnisexSet,
|
||||
personGender,
|
||||
personIsPlural,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { isAdjectiveEntry, isLocativeAdverbEntry } from "./type-predicates";
|
||||
import {
|
||||
typePredicates as tp,
|
||||
psStringFromEntry,
|
||||
} from "./text-tools";
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
|
||||
export function evaluateCompliment(c: Compliment, person: T.Person): { ps: T.PsString[], e: string } {
|
||||
const e = getEnglishWord(c.entry);
|
||||
|
@ -17,13 +15,13 @@ export function evaluateCompliment(c: Compliment, person: T.Person): { ps: T.PsS
|
|||
console.log(e);
|
||||
throw new Error("error getting english for compliment");
|
||||
}
|
||||
if (isLocativeAdverbEntry(c.entry)) {
|
||||
if (tp.isLocativeAdverbEntry(c.entry)) {
|
||||
return {
|
||||
ps: [psStringFromEntry(c.entry)],
|
||||
e,
|
||||
};
|
||||
}
|
||||
if (isAdjectiveEntry(c.entry)) {
|
||||
if (tp.isAdjectiveEntry(c.entry)) {
|
||||
const infs = inflectWord(c.entry);
|
||||
if (!infs) return {
|
||||
ps: [psStringFromEntry(c.entry)],
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
getPersonFromVerbForm,
|
||||
concatPsString,
|
||||
removeAccents,
|
||||
typePredicates as tp,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import {
|
||||
personFromNP,
|
||||
|
@ -13,7 +14,6 @@ import {
|
|||
import {
|
||||
evaluateCompliment,
|
||||
} from "./compliment-tools";
|
||||
import { isPluralNounEntry } from "./type-predicates";
|
||||
|
||||
// Equative Rules
|
||||
// - An equative equates a SUBJECT: Noun Phrase and a PREDICATE: Noun Phrase | Compliment
|
||||
|
@ -84,7 +84,7 @@ function makeEquative(e: EquativeClause) {
|
|||
? "past"
|
||||
: e.tense;
|
||||
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
|
||||
: subjP
|
||||
const pashtoPerson = (e.subject.type === "pronoun")
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { isMascNounEntry, isPluralNounEntry, isUnisexNounEntry } from "./type-predicates";
|
||||
import {
|
||||
Types as T,
|
||||
getEnglishWord,
|
||||
|
@ -6,16 +5,15 @@ import {
|
|||
getVerbBlockPosFromPerson,
|
||||
grammarUnits,
|
||||
inflectWord,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import {
|
||||
psStringFromEntry,
|
||||
} from "./text-tools";
|
||||
typePredicates as tp,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
|
||||
function getRandPers(): T.Person {
|
||||
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
|
||||
if (!a) {
|
||||
return getRandPers();
|
||||
|
@ -99,9 +97,9 @@ export function evaluateNP(np: NounPhrase): { ps: T.PsString[], e: string } {
|
|||
}
|
||||
|
||||
function nounGender(n: Noun): T.Gender {
|
||||
const nGender = isUnisexNounEntry(n.entry)
|
||||
const nGender = tp.isUnisexNounEntry(n.entry)
|
||||
? "unisex"
|
||||
: isMascNounEntry(n.entry)
|
||||
: tp.isMascNounEntry(n.entry)
|
||||
? "masc"
|
||||
: "fem";
|
||||
return (nGender === "unisex" && n.gender)
|
||||
|
@ -112,7 +110,7 @@ function nounGender(n: Noun): T.Gender {
|
|||
}
|
||||
|
||||
function nounNumber(n: Noun): NounNumber {
|
||||
const nNumber = isPluralNounEntry(n.entry)
|
||||
const nNumber = tp.isPluralNounEntry(n.entry)
|
||||
? "plural"
|
||||
: "singular";
|
||||
return nNumber === "plural"
|
||||
|
@ -215,4 +213,3 @@ function getEnglishFromNoun(entry: T.DictionaryEntry, number: NounNumber): strin
|
|||
}
|
||||
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 {
|
||||
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);
|
||||
return [
|
||||
...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 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 useStickyState from "./useStickyState";
|
||||
import {
|
||||
AT,
|
||||
getUser,
|
||||
userObjIsEqual,
|
||||
} from "@lingdocs/lingdocs-main";
|
||||
import {
|
||||
useStickyState,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { CronJob } from "cron";
|
||||
import { postSavedResults } from "./lib/game-results";
|
||||
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import rawWords from "./raw-words";
|
||||
import {
|
||||
isAdjectiveEntry,
|
||||
isNounEntry,
|
||||
isVerbEntry,
|
||||
isAdverbEntry,
|
||||
} from "../lib/type-predicates";
|
||||
typePredicates as tp,
|
||||
Types as T,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { categorize } from "../lib/categorize";
|
||||
|
||||
const words = categorize<Entry, Words>(rawWords, {
|
||||
"nouns": isNounEntry,
|
||||
"adjectives": isAdjectiveEntry,
|
||||
"verbs": isVerbEntry,
|
||||
"adverbs": isAdverbEntry,
|
||||
const words = categorize<T.Entry, T.Words>(rawWords, {
|
||||
"nouns": tp.isNounEntry,
|
||||
"adjectives": tp.isAdjectiveEntry,
|
||||
"verbs": tp.isVerbEntry,
|
||||
"adverbs": tp.isAdverbEntry,
|
||||
});
|
||||
|
||||
export default words;
|
||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -1684,14 +1684,15 @@
|
|||
pbf "^3.2.1"
|
||||
rambda "^6.7.0"
|
||||
|
||||
"@lingdocs/pashto-inflector@^1.7.0":
|
||||
version "1.7.0"
|
||||
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.7.0.tgz#214b0be0528d3c4017db78255c61b83417d20c35"
|
||||
integrity sha512-/7CrJx1KGO4xPJOkWG9LLEteEdeQ20MA5oX4AI10uQuaZpYULR5oF1J02mwS21wERoMUYjCwMrcZ+G71w/T34A==
|
||||
"@lingdocs/pashto-inflector@^1.8.0":
|
||||
version "1.8.0"
|
||||
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.8.0.tgz#51d4d55cc9a242feb29556136096a4bcfb5474f1"
|
||||
integrity sha512-EE2F9mEs6px6jpolz75eHoztC4EDJ3090AhKKDaTvun3O5pRSu69yGJJpyAc1sj1SphbQeZSmTdwNAiR43RMBg==
|
||||
dependencies:
|
||||
classnames "^2.2.6"
|
||||
pbf "^3.2.1"
|
||||
rambda "^6.7.0"
|
||||
react-select "^5.2.2"
|
||||
|
||||
"@mdx-js/mdx@^0.15.5":
|
||||
version "0.15.7"
|
||||
|
@ -10486,6 +10487,19 @@ react-select@^5.1.0:
|
|||
prop-types "^15.6.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:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-smooth-collapse/-/react-smooth-collapse-2.1.0.tgz#64be7af94b61d6d203d9a09af0fba671f9b5983c"
|
||||
|
|
Loading…
Reference in New Issue