brought in new verb explorer and actually fixed roots and stems crash problem

This commit is contained in:
lingdocs 2022-04-08 15:41:11 +05:00
parent 759df4811e
commit f306488e0d
48 changed files with 149 additions and 3179 deletions

View File

@ -5,7 +5,7 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.15.4", "@fortawesome/fontawesome-free": "^5.15.4",
"@lingdocs/lingdocs-main": "^0.2.0", "@lingdocs/lingdocs-main": "^0.2.0",
"@lingdocs/pashto-inflector": "^1.7.0", "@lingdocs/pashto-inflector": "^1.8.0",
"@testing-library/jest-dom": "^5.11.4", "@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0", "@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10", "@testing-library/user-event": "^12.1.10",

View File

@ -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;

View File

@ -6,9 +6,10 @@ import {
inflectWord, inflectWord,
defaultTextOptions as opts, defaultTextOptions as opts,
getEnglishWord, getEnglishWord,
Types as T,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
function InflectionCarousel({ items }: { items: (NounEntry | AdjectiveEntry)[] }) { function InflectionCarousel({ items }: { items: (T.NounEntry | T.AdjectiveEntry)[] }) {
if (!items.length) { if (!items.length) {
return "no items for carousel"; return "no items for carousel";
} }

View File

@ -1,8 +1,10 @@
// TODO: DEPRECATE THIS AND USE THE PASHTO INFLECTER NP PRONOUN PICKER!
import { import {
Types as T, Types as T,
ButtonSelect, ButtonSelect,
useStickyState,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import useStickyState from "../../useStickyState";
const gColors = { const gColors = {
masc: "LightSkyBlue", masc: "LightSkyBlue",
@ -143,4 +145,3 @@ function PronounPicker({ onChange, pronoun, isObject }: {
}; };
export default PronounPicker; export default PronounPicker;

View File

@ -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;

View File

@ -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;

View File

@ -5,6 +5,7 @@ import {
Types as T, Types as T,
personGender, personGender,
personIsPlural, personIsPlural,
typePredicates,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import { import {
ExplorerState, ExplorerState,
@ -14,13 +15,13 @@ import {
equativeMachine, equativeMachine,
assembleEquativeOutput, assembleEquativeOutput,
} from "../../lib/equative-machine"; } from "../../lib/equative-machine";
import { const {
isUnisexNounEntry, isUnisexNounEntry,
isAdjectiveEntry, isAdjectiveEntry,
isVerbEntry, isVerbEntry,
isLocativeAdverbEntry, isLocativeAdverbEntry,
isNounEntry, isNounEntry,
} from "../../lib/type-predicates"; } = typePredicates;
function chooseLength<O>(o: T.SingleOrLengthOpts<O>, length: "short" | "long"): O { function chooseLength<O>(o: T.SingleOrLengthOpts<O>, length: "short" | "long"): O {
return ("long" in o) ? o[length] : o; return ("long" in o) ? o[length] : o;
@ -54,14 +55,14 @@ function SingleItemDisplay({ state }: { state: ExplorerState }) {
} }
} }
function makeComplement(entry: AdjectiveEntry | LocativeAdverbEntry): Compliment { function makeComplement(entry: T.AdjectiveEntry | T.LocativeAdverbEntry): Compliment {
return { return {
type: "compliment", type: "compliment",
entry, entry,
}; };
} }
function makeNounPhrase(entry: NounEntry | UnisexNounEntry | VerbEntry, state: ExplorerState, entity: "subject" | "predicate"): NounPhrase { function makeNounPhrase(entry: T.NounEntry | T.UnisexNounEntry | T.VerbEntry, state: ExplorerState, entity: "subject" | "predicate"): NounPhrase {
if (isVerbEntry(entry)) { if (isVerbEntry(entry)) {
return { return {
type: "participle", type: "participle",
@ -76,7 +77,7 @@ function makeNounPhrase(entry: NounEntry | UnisexNounEntry | VerbEntry, state: E
}; };
} }
export function makeBlockWPronouns(e: AdjectiveEntry | UnisexNounEntry | LocativeAdverbEntry, tense: EquativeTense, negative: boolean, length?: "short" | "long"): T.SingleOrLengthOpts<T.VerbBlock> { export function makeBlockWPronouns(e: T.AdjectiveEntry | T.UnisexNounEntry | T.LocativeAdverbEntry, tense: EquativeTense, negative: boolean, length?: "short" | "long"): T.SingleOrLengthOpts<T.VerbBlock> {
// if the output's gonna have long / short forms (if it's past or wouldBe) then recursive call to make the long and short versions // if the output's gonna have long / short forms (if it's past or wouldBe) then recursive call to make the long and short versions
if (!length && "long" in assembleEquativeOutput(equativeMachine({ if (!length && "long" in assembleEquativeOutput(equativeMachine({
subject: { type: "pronoun", pronounType: "near", person: 0 }, subject: { type: "pronoun", pronounType: "near", person: 0 },

View File

@ -1,10 +1,10 @@
import { nouns, adjectives, verbs, adverbs } from "../../words/words"; import { nouns, adjectives, verbs, adverbs } from "../../words/words";
import { import {
isLocativeAdverbEntry, Types as T,
isUnisexNounEntry, typePredicates as tp,
} from "../../lib/type-predicates"; } from "@lingdocs/pashto-inflector";
function sort<T extends (AdjectiveEntry | NounEntry | VerbEntry | AdverbEntry)>(arr: Readonly<T[]>): T[] { function sort<T extends (T.AdjectiveEntry | T.NounEntry | T.VerbEntry | T.AdverbEntry)>(arr: Readonly<T[]>): T[] {
if ("entry" in arr[0]) { if ("entry" in arr[0]) {
return [...arr].sort((a, b) => ( return [...arr].sort((a, b) => (
// @ts-ignore // @ts-ignore
@ -17,15 +17,15 @@ function sort<T extends (AdjectiveEntry | NounEntry | VerbEntry | AdverbEntry)>(
)); ));
} }
const unisexNouns = sort(nouns.filter(x => isUnisexNounEntry(x)) as UnisexNounEntry[]); const unisexNouns = sort(nouns.filter(x => tp.isUnisexNounEntry(x)) as T.UnisexNounEntry[]);
const nonUnisexNouns = sort(nouns.filter(x => !isUnisexNounEntry(x)) as (MascNounEntry | FemNounEntry)[]); const nonUnisexNouns = sort(nouns.filter(x => !tp.isUnisexNounEntry(x)) as (T.MascNounEntry | T.FemNounEntry)[]);
const inputs = { const inputs = {
adjective: sort(adjectives), adjective: sort(adjectives),
unisexNoun: unisexNouns, unisexNoun: unisexNouns,
noun: nonUnisexNouns, noun: nonUnisexNouns,
participle: sort(verbs), participle: sort(verbs),
adverb: sort(adverbs.filter(isLocativeAdverbEntry)), adverb: sort(adverbs.filter(tp.isLocativeAdverbEntry)),
}; };
export const defaultAdjective = inputs.adjective.find(ps => ps.p === "زوړ") || inputs.adjective[0]; export const defaultAdjective = inputs.adjective.find(ps => ps.p === "زوړ") || inputs.adjective[0];

View File

@ -1,9 +1,12 @@
import inputs from "./explorer-inputs"; import inputs from "./explorer-inputs";
import { ExplorerState, ExplorerReducerAction } from "./explorer-types"; import { ExplorerState, ExplorerReducerAction } from "./explorer-types";
import {
Types as T,
} from "@lingdocs/pashto-inflector";
export function reducer(state: ExplorerState, action: ExplorerReducerAction): ExplorerState { export function reducer(state: ExplorerState, action: ExplorerReducerAction): ExplorerState {
if (action.type === "setPredicate") { if (action.type === "setPredicate") {
const pile = inputs[state.predicate.type] as (UnisexNounEntry | AdjectiveEntry)[]; const pile = inputs[state.predicate.type] as (T.UnisexNounEntry | T.AdjectiveEntry)[];
const predicate = (pile.find(p => p.ts === action.payload) || pile[0]); const predicate = (pile.find(p => p.ts === action.payload) || pile[0]);
return { return {
...state, ...state,

View File

@ -14,9 +14,17 @@ import {
getEnglishWord, getEnglishWord,
Types as T, Types as T,
removeFVarients, removeFVarients,
typePredicates,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import { isAdjectiveEntry, isAdverbEntry, isFemNounEntry, isMascNounEntry, isNounEntry, isPluralNounEntry } from "../../lib/type-predicates";
import Select from "react-select"; import Select from "react-select";
const {
isAdjectiveEntry,
isAdverbEntry,
isFemNounEntry,
isMascNounEntry,
isNounEntry,
isPluralNounEntry,
} = typePredicates;
const zIndexProps = { const zIndexProps = {
menuPortalTarget: document.body, menuPortalTarget: document.body,
@ -83,7 +91,7 @@ export function InputSelector({ state, dispatch, entity }: {
} }
const type = state[entity].type; const type = state[entity].type;
const entry: NounEntry | VerbEntry | AdjectiveEntry | LocativeAdverbEntry | undefined = type === "pronouns" const entry: T.NounEntry | T.VerbEntry | T.AdjectiveEntry | T.LocativeAdverbEntry | undefined = type === "pronouns"
? undefined ? undefined
// @ts-ignore // @ts-ignore
: state[entity][type]; : state[entity][type];
@ -164,7 +172,7 @@ function GenderAndNumberSelect({ state, dispatch, entity }: {
</div>; </div>;
} }
function makeOption(e: VerbEntry | NounEntry | AdjectiveEntry | LocativeAdverbEntry): { value: string, label: string } { function makeOption(e: T.VerbEntry | T.NounEntry | T.AdjectiveEntry | T.LocativeAdverbEntry): { value: string, label: string } {
const entry = "entry" in e ? e.entry : e; const entry = "entry" in e ? e.entry : e;
// TODO: THIS IS SUUUPER SKETCH // TODO: THIS IS SUUUPER SKETCH
const eng = (isNounEntry(e) || isAdjectiveEntry(e) || isAdverbEntry(e)) const eng = (isNounEntry(e) || isAdjectiveEntry(e) || isAdverbEntry(e))

View File

@ -17,14 +17,14 @@ export type SubjectEntityInfo = EntitiyInfo & { type: SubjectType };
export type PredicateEntityInfo = EntitiyInfo & { export type PredicateEntityInfo = EntitiyInfo & {
type: PredicateType, type: PredicateType,
adjective: AdjectiveEntry, adjective: import("@lingdocs/pashto-inflector").Types.AdjectiveEntry,
adverb: LocativeAdverbEntry, adverb: import("@lingdocs/pashto-inflector").Types.LocativeAdverbEntry,
} }
type EntitiyInfo = { type EntitiyInfo = {
noun: NounEntry, noun: import("@lingdocs/pashto-inflector").Types.NounEntry,
participle: VerbEntry, participle: import("@lingdocs/pashto-inflector").Types.VerbEntry,
unisexNoun: UnisexNounEntry, unisexNoun: import("@lingdocs/pashto-inflector").Types.UnisexNounEntry,
info: { info: {
number: NounNumber, number: NounNumber,
gender: T.Gender, gender: T.Gender,

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
};
},
} : {},
};
}

View File

@ -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;

View File

@ -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;

View File

@ -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,
}
};
}

View File

@ -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;

View File

@ -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",
};
},
} : {},
};
}

View File

@ -22,6 +22,7 @@ import {
removeFVarients, removeFVarients,
InflectionsTable, InflectionsTable,
inflectWord, inflectWord,
typePredicates as tp,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import shuffle from "../../lib/shuffle-array"; import shuffle from "../../lib/shuffle-array";
import InflectionCarousel from "../../components/InflectionCarousel"; import InflectionCarousel from "../../components/InflectionCarousel";
@ -29,12 +30,8 @@ import { nouns } from "../../words/words";
import Link from "../../components/Link"; import Link from "../../components/Link";
import Table from "../../components/Table"; import Table from "../../components/Table";
import { startingWord } from "../../lib/starting-word"; import { startingWord } from "../../lib/starting-word";
import {
isFemNounEntry,
isPattern6FemEntry,
} from "../../lib/type-predicates";
export const femNouns = nouns.filter(isFemNounEntry); export const femNouns = nouns.filter(tp.isFemNounEntry);
The <Link to="/inflection/inflection-patterns/">5 basic patterns in the last chapter</Link> work with both masculine and feminine words (nouns and adjectives). The <Link to="/inflection/inflection-patterns/">5 basic patterns in the last chapter</Link> work with both masculine and feminine words (nouns and adjectives).
@ -42,6 +39,6 @@ There are also a few more patterns that are only for **feminine nouns**.
## Feminine Nouns Ending in <InlinePs opts={opts} ps={{ p: "ي", f: "ee" }} /> ## Feminine Nouns Ending in <InlinePs opts={opts} ps={{ p: "ي", f: "ee" }} />
<InflectionCarousel items={startingWord(femNouns.filter(isPattern6FemEntry), "آزادي")} /> <InflectionCarousel items={startingWord(femNouns.filter(tp.isPattern6FemEntry), "آزادي")} />
**Note:** This only works with **inanimate nouns**. (ie. words for people like <InlinePs opts={opts} ps={{ p: "باجي", f: "baajée", e: "older sister" }} /> will not inflect like this.) **Note:** This only works with **inanimate nouns**. (ie. words for people like <InlinePs opts={opts} ps={{ p: "باجي", f: "baajée", e: "older sister" }} /> will not inflect like this.)

View File

@ -21,24 +21,15 @@ import {
grammarUnits, grammarUnits,
InflectionsTable, InflectionsTable,
inflectWord, inflectWord,
typePredicates as tp,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import {
isNounOrVerb,
isPattern1Entry,
isPattern2Entry,
isPattern3Entry,
isPattern4Entry,
isPattern5Entry,
isUnisexNounEntry,
} from "../../lib/type-predicates";
import InflectionCarousel from "../../components/InflectionCarousel"; import InflectionCarousel from "../../components/InflectionCarousel";
import { nouns, adjectives } from "../../words/words"; import { nouns, adjectives } from "../../words/words";
import { startingWord } from "../../lib/starting-word"; import { startingWord } from "../../lib/starting-word";
import Link from "../../components/Link"; import Link from "../../components/Link";
import psmd from "../../lib/psmd"; import psmd from "../../lib/psmd";
export const words = [ export const words = [
...nouns.filter(isUnisexNounEntry), ...nouns.filter(tp.isUnisexNounEntry),
...adjectives, ...adjectives,
]; ];
@ -58,7 +49,7 @@ These words always end in:
- **Feminine:** - <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} /> - **Feminine:** - <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} />
<InflectionCarousel items={startingWord( <InflectionCarousel items={startingWord(
words.filter(isPattern1Entry), words.filter(tp.isPattern1Entry),
"غټ", "غټ",
)} /> )} />
@ -93,7 +84,7 @@ In any other kind of sandwich the first inflection would be used.
## 2. Words ending in an unstressed <InlinePs opts={opts} ps={{ p: "ی", f: "ey" }} /> ## 2. Words ending in an unstressed <InlinePs opts={opts} ps={{ p: "ی", f: "ey" }} />
<InflectionCarousel items={startingWord( <InflectionCarousel items={startingWord(
words.filter(isPattern2Entry), words.filter(tp.isPattern2Entry),
"ستړی", "ستړی",
)} /> )} />
@ -102,7 +93,7 @@ In any other kind of sandwich the first inflection would be used.
This is very similar to pattern #2, but with the stress on the last syllable the feminine inflection changes. This is very similar to pattern #2, but with the stress on the last syllable the feminine inflection changes.
<InflectionCarousel items={startingWord( <InflectionCarousel items={startingWord(
words.filter(isPattern3Entry), words.filter(tp.isPattern3Entry),
"لومړی", "لومړی",
)} /> )} />
@ -114,7 +105,7 @@ These words are a little irregular but you can see a common patten based around:
- shortening the other forms and adding the <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} />, <InlinePs opts={opts} ps={{ p: "ـې", f: "-e" }} />, <InlinePs opts={opts} ps={{ p: "ـو", f: "-o" }} /> endings - shortening the other forms and adding the <InlinePs opts={opts} ps={{ p: "ـه", f: "-a" }} />, <InlinePs opts={opts} ps={{ p: "ـې", f: "-e" }} />, <InlinePs opts={opts} ps={{ p: "ـو", f: "-o" }} /> endings
<InflectionCarousel items={startingWord( <InflectionCarousel items={startingWord(
words.filter(isPattern4Entry), words.filter(tp.isPattern4Entry),
"پښتون", "پښتون",
)} /> )} />
@ -157,7 +148,7 @@ But it will of course inflect for the plural.
These are also a little irregular but instead of lengthening the 1st masculine inflection they compress it as well and take just an <InlinePs opts={opts} ps={{ p: "ـه", f: "-u" }} /> on the end. These are also a little irregular but instead of lengthening the 1st masculine inflection they compress it as well and take just an <InlinePs opts={opts} ps={{ p: "ـه", f: "-u" }} /> on the end.
<InflectionCarousel items={startingWord( <InflectionCarousel items={startingWord(
words.filter(isPattern5Entry), words.filter(tp.isPattern5Entry),
"غل", "غل",
)} /> )} />

View File

@ -6,9 +6,9 @@ import {
defaultTextOptions as opts, defaultTextOptions as opts,
InlinePs, InlinePs,
Examples, Examples,
firstVariation,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import genderColors from "../../lib/gender-colors"; import genderColors from "../../lib/gender-colors";
import { firstVariation } from "../../lib/text-tools";
import GenderTable from "../../components/GenderTable"; import GenderTable from "../../components/GenderTable";
import Link from "../../components/Link"; import Link from "../../components/Link";
export const femColor = genderColors.f; export const femColor = genderColors.f;

View File

@ -3,13 +3,13 @@ title: Phrase Builder
fullWidth: true fullWidth: true
--- ---
import PhraseBuilder from "../../components/phrase-builder/PhraseBuilder";
import { import {
VPExplorer,
defaultTextOptions, defaultTextOptions,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import { nouns, verbs } from "../../words/words"; import { nouns, verbs } from "../../words/words";
<PhraseBuilder <VPExplorer
opts={defaultTextOptions} opts={defaultTextOptions}
nouns={nouns} nouns={nouns}
verbs={verbs} verbs={verbs}

View File

@ -2,18 +2,16 @@
title: Pronoun Picker title: Pronoun Picker
--- ---
import PronounPicker from "../../components/np-picker/PronounPicker"; import PronounPicker from "../../components/PronounPicker";
import { import {
defaultTextOptions as opts, defaultTextOptions as opts,
InlinePs, InlinePs,
ButtonSelect, ButtonSelect,
} from "@lingdocs/pashto-inflector";
import { useState } from "react";
import {
randomPerson, randomPerson,
randomSubjObj, randomSubjObj,
isInvalidSubjObjCombo, isInvalidSubjObjCombo,
} from "../../lib/np-tools"; } from "@lingdocs/pashto-inflector";
import { useState } from "react";
export function RPicker() { export function RPicker() {
// TODO: Pull this into a type safe file // TODO: Pull this into a type safe file

View File

@ -33,7 +33,7 @@ import verbTreeImperfectivePerfective from "./verb-tree-imperfective-perfective.
export const opts = defaultTextOptions; export const opts = defaultTextOptions;
export const verbs = verbsRaw.filter(v => !v.c?.includes("comp.")); export const verbs = verbsRaw.filter(v => !("complement" in v));
export function InfoCarousel({ items, highlighted, hidePastParticiple }) { export function InfoCarousel({ items, highlighted, hidePastParticiple }) {
return ( return (

View File

@ -12,44 +12,38 @@ import {
pashtoConsonants, pashtoConsonants,
inflectWord, inflectWord,
isUnisexSet, isUnisexSet,
typePredicates as tp,
firstVariation,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import { nouns } from "../../words/words"; import { nouns } from "../../words/words";
import {
firstVariation,
} from "../../lib/text-tools";
import {
isMascNounEntry,
isFemNounEntry,
isUnisexNounEntry,
} from "../../lib/type-predicates";
import { categorize } from "../../lib/categorize"; import { categorize } from "../../lib/categorize";
const genders: T.Gender[] = ["masc", "fem"]; const genders: T.Gender[] = ["masc", "fem"];
const mascNouns = nouns.filter(isMascNounEntry); const mascNouns = nouns.filter(tp.isMascNounEntry);
const femNouns = [ const femNouns = [
...nouns.filter(isFemNounEntry), ...nouns.filter(tp.isFemNounEntry),
...getFemVersions(mascNouns.filter(isUnisexNounEntry)), ...getFemVersions(mascNouns.filter(tp.isUnisexNounEntry)),
]; ];
const types = { const types = {
masc: categorize<MascNounEntry, { masc: categorize<T.MascNounEntry, {
consonantMasc: MascNounEntry[], consonantMasc: T.MascNounEntry[],
eyMasc: MascNounEntry[], eyMasc: T.MascNounEntry[],
uMasc: MascNounEntry[], uMasc: T.MascNounEntry[],
yMasc: MascNounEntry[], yMasc: T.MascNounEntry[],
}>(mascNouns, { }>(mascNouns, {
consonantMasc: endsWith([{ p: pashtoConsonants }, { p: "و", f: "w" }]), consonantMasc: endsWith([{ p: pashtoConsonants }, { p: "و", f: "w" }]),
eyMasc: endsWith({ p: "ی", f: "ey" }), eyMasc: endsWith({ p: "ی", f: "ey" }),
uMasc: endsWith({ p: "ه", f: "u" }), uMasc: endsWith({ p: "ه", f: "u" }),
yMasc: endsWith([{ p: "ای", f: "aay" }, { p: "وی", f: "ooy" }]), yMasc: endsWith([{ p: "ای", f: "aay" }, { p: "وی", f: "ooy" }]),
}), }),
fem: categorize<FemNounEntry, { fem: categorize<T.FemNounEntry, {
aaFem: FemNounEntry[], aaFem: T.FemNounEntry[],
eeFem: FemNounEntry[], eeFem: T.FemNounEntry[],
uyFem: FemNounEntry[], uyFem: T.FemNounEntry[],
aFem: FemNounEntry[], aFem: T.FemNounEntry[],
eFem: FemNounEntry[], eFem: T.FemNounEntry[],
}>(femNouns, { }>(femNouns, {
aaFem: endsWith({ p: "ا", f: "aa" }), aaFem: endsWith({ p: "ا", f: "aa" }),
eeFem: endsWith({ p: "ي", f: "ee" }), eeFem: endsWith({ p: "ي", f: "ee" }),
@ -59,7 +53,7 @@ const types = {
}), }),
}; };
function getFemVersions(uns: UnisexNounEntry[]): FemNounEntry[] { function getFemVersions(uns: T.UnisexNounEntry[]): T.FemNounEntry[] {
return uns.map((n) => { return uns.map((n) => {
const infs = inflectWord(n); const infs = inflectWord(n);
if (!infs || !infs.inflections) return undefined; if (!infs || !infs.inflections) return undefined;
@ -68,18 +62,18 @@ function getFemVersions(uns: UnisexNounEntry[]): FemNounEntry[] {
e: n.e, e: n.e,
...infs.inflections.fem[0][0], ...infs.inflections.fem[0][0],
} as T.DictionaryEntry; } as T.DictionaryEntry;
}).filter(n => !!n) as FemNounEntry[]; }).filter(n => !!n) as T.FemNounEntry[];
} }
function flatten<T>(o: Record<string, T[]>): T[] { function flatten<T>(o: Record<string, T[]>): T[] {
return Object.values(o).flat(); return Object.values(o).flat();
} }
function nounNotIn(st: NounEntry[]): (n: NounEntry | T.DictionaryEntry) => boolean { function nounNotIn(st: T.NounEntry[]): (n: T.NounEntry | T.DictionaryEntry) => boolean {
return (n: T.DictionaryEntry) => !st.find(x => x.ts === n.ts); return (n: T.DictionaryEntry) => !st.find(x => x.ts === n.ts);
} }
type CategorySet = Record<string, NounEntry[]>; type CategorySet = Record<string, T.NounEntry[]>;
// for some reason we need to use this CategorySet type here... 🤷‍♂️ // for some reason we need to use this CategorySet type here... 🤷‍♂️
const exceptions: Record<string, CategorySet> = { const exceptions: Record<string, CategorySet> = {
masc: { masc: {

View File

@ -12,17 +12,13 @@ import {
defaultTextOptions as opts, defaultTextOptions as opts,
inflectWord, inflectWord,
standardizePashto, standardizePashto,
firstVariation,
typePredicates as tp,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import { nouns } from "../../words/words"; import { nouns } from "../../words/words";
import { intoPatterns } from "../../lib/categorize"; import { intoPatterns } from "../../lib/categorize";
import {
firstVariation,
} from "../../lib/text-tools";
import {
isUnisexNounEntry,
} from "../../lib/type-predicates";
const unisexNouns = nouns.filter(isUnisexNounEntry); const unisexNouns = nouns.filter(tp.isUnisexNounEntry);
type NType = "pattern1" | "pattern2" | "pattern3" | "pattern4" | "pattern5" | "other"; type NType = "pattern1" | "pattern2" | "pattern3" | "pattern4" | "pattern5" | "other";
// TODO: make pattern types as overlay types // TODO: make pattern types as overlay types
const types = intoPatterns(unisexNouns); const types = intoPatterns(unisexNouns);
@ -41,7 +37,7 @@ export default function UnisexNounGame({ id, link }: { id: string, link: string
do { do {
type = getRandomFromList(keys); type = getRandomFromList(keys);
} while (!pool[type].length); } while (!pool[type].length);
const entry = getRandomFromList<UnisexNounEntry>( const entry = getRandomFromList<T.UnisexNounEntry>(
// @ts-ignore // @ts-ignore
pool[type] pool[type]
); );

View File

@ -1,10 +1,16 @@
import { import {
typePredicates,
// super weird, need to es-lint disable this this
// eslint-disable-next-line
Types as T,
} from "@lingdocs/pashto-inflector";
const {
isPattern1Entry, isPattern1Entry,
isPattern2Entry, isPattern2Entry,
isPattern3Entry, isPattern3Entry,
isPattern4Entry, isPattern4Entry,
isPattern5Entry, isPattern5Entry,
} from "./type-predicates"; } = typePredicates;
/** /**
* sorts a given array of on type into a typed object of arrays of subtypes, based on predicates * sorts a given array of on type into a typed object of arrays of subtypes, based on predicates
@ -75,22 +81,22 @@ export function categorize<I, X extends Record<string, I[]>>(
// TODO: uncategorizable words like ایرې - n. f. pl. -- could be pattern 1 or 2 🤷‍♂️ // TODO: uncategorizable words like ایرې - n. f. pl. -- could be pattern 1 or 2 🤷‍♂️
export function intoPatterns<T extends (NounEntry | AdjectiveEntry)>(words: T[]): { export function intoPatterns<T extends (T.NounEntry | T.AdjectiveEntry)>(words: T[]): {
"pattern1": Pattern1Entry<T>[], "pattern1": T.Pattern1Entry<T>[],
"pattern2": Pattern2Entry<T>[], "pattern2": T.Pattern2Entry<T>[],
"pattern3": Pattern3Entry<T>[], "pattern3": T.Pattern3Entry<T>[],
"pattern4": Pattern4Entry<T>[], "pattern4": T.Pattern4Entry<T>[],
"pattern5": Pattern5Entry<T>[], "pattern5": T.Pattern5Entry<T>[],
"other": NonInflecting<T>[], "other": T.NonInflecting<T>[],
// "pattern6fem": Pattern6FemNoun<T>[], // "pattern6fem": Pattern6FemNoun<T>[],
} { } {
return categorize<(NounEntry | AdjectiveEntry), { return categorize<(T.NounEntry | T.AdjectiveEntry), {
"pattern1": Pattern1Entry<T>[], "pattern1": T.Pattern1Entry<T>[],
"pattern2": Pattern2Entry<T>[], "pattern2": T.Pattern2Entry<T>[],
"pattern3": Pattern3Entry<T>[], "pattern3": T.Pattern3Entry<T>[],
"pattern4": Pattern4Entry<T>[], "pattern4": T.Pattern4Entry<T>[],
"pattern5": Pattern5Entry<T>[], "pattern5": T.Pattern5Entry<T>[],
"other": NonInflecting<T>[], "other": T.NonInflecting<T>[],
// "pattern6fem": Pattern6FemNoun<T>[], // "pattern6fem": Pattern6FemNoun<T>[],
}>( }>(
words, words,

View File

@ -5,11 +5,9 @@ import {
isUnisexSet, isUnisexSet,
personGender, personGender,
personIsPlural, personIsPlural,
} from "@lingdocs/pashto-inflector"; typePredicates as tp,
import { isAdjectiveEntry, isLocativeAdverbEntry } from "./type-predicates";
import {
psStringFromEntry, psStringFromEntry,
} from "./text-tools"; } from "@lingdocs/pashto-inflector";
export function evaluateCompliment(c: Compliment, person: T.Person): { ps: T.PsString[], e: string } { export function evaluateCompliment(c: Compliment, person: T.Person): { ps: T.PsString[], e: string } {
const e = getEnglishWord(c.entry); const e = getEnglishWord(c.entry);
@ -17,13 +15,13 @@ export function evaluateCompliment(c: Compliment, person: T.Person): { ps: T.PsS
console.log(e); console.log(e);
throw new Error("error getting english for compliment"); throw new Error("error getting english for compliment");
} }
if (isLocativeAdverbEntry(c.entry)) { if (tp.isLocativeAdverbEntry(c.entry)) {
return { return {
ps: [psStringFromEntry(c.entry)], ps: [psStringFromEntry(c.entry)],
e, e,
}; };
} }
if (isAdjectiveEntry(c.entry)) { if (tp.isAdjectiveEntry(c.entry)) {
const infs = inflectWord(c.entry); const infs = inflectWord(c.entry);
if (!infs) return { if (!infs) return {
ps: [psStringFromEntry(c.entry)], ps: [psStringFromEntry(c.entry)],

View File

@ -5,6 +5,7 @@ import {
getPersonFromVerbForm, getPersonFromVerbForm,
concatPsString, concatPsString,
removeAccents, removeAccents,
typePredicates as tp,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import { import {
personFromNP, personFromNP,
@ -13,7 +14,6 @@ import {
import { import {
evaluateCompliment, evaluateCompliment,
} from "./compliment-tools"; } from "./compliment-tools";
import { isPluralNounEntry } from "./type-predicates";
// Equative Rules // Equative Rules
// - An equative equates a SUBJECT: Noun Phrase and a PREDICATE: Noun Phrase | Compliment // - An equative equates a SUBJECT: Noun Phrase and a PREDICATE: Noun Phrase | Compliment
@ -84,7 +84,7 @@ function makeEquative(e: EquativeClause) {
? "past" ? "past"
: e.tense; : e.tense;
const subjP = personFromNP(e.subject); const subjP = personFromNP(e.subject);
const englishPerson = (e.subject.type === "participle" || (e.subject.type === "noun" && isPluralNounEntry(e.subject.entry))) const englishPerson = (e.subject.type === "participle" || (e.subject.type === "noun" && tp.isPluralNounEntry(e.subject.entry)))
? T.Person.ThirdSingMale ? T.Person.ThirdSingMale
: subjP : subjP
const pashtoPerson = (e.subject.type === "pronoun") const pashtoPerson = (e.subject.type === "pronoun")

View File

@ -1,4 +1,3 @@
import { isMascNounEntry, isPluralNounEntry, isUnisexNounEntry } from "./type-predicates";
import { import {
Types as T, Types as T,
getEnglishWord, getEnglishWord,
@ -6,16 +5,15 @@ import {
getVerbBlockPosFromPerson, getVerbBlockPosFromPerson,
grammarUnits, grammarUnits,
inflectWord, inflectWord,
} from "@lingdocs/pashto-inflector";
import {
psStringFromEntry, psStringFromEntry,
} from "./text-tools"; typePredicates as tp,
} from "@lingdocs/pashto-inflector";
function getRandPers(): T.Person { function getRandPers(): T.Person {
return Math.floor(Math.random() * 12); return Math.floor(Math.random() * 12);
} }
export function randomPerson(a?: { prev?: T.Person, counterPart?: VerbObject | NPSelection }) { export function randomPerson(a?: { prev?: T.Person, counterPart?: T.VerbObject | T.NPSelection }) {
// no restrictions, just get any person // no restrictions, just get any person
if (!a) { if (!a) {
return getRandPers(); return getRandPers();
@ -99,9 +97,9 @@ export function evaluateNP(np: NounPhrase): { ps: T.PsString[], e: string } {
} }
function nounGender(n: Noun): T.Gender { function nounGender(n: Noun): T.Gender {
const nGender = isUnisexNounEntry(n.entry) const nGender = tp.isUnisexNounEntry(n.entry)
? "unisex" ? "unisex"
: isMascNounEntry(n.entry) : tp.isMascNounEntry(n.entry)
? "masc" ? "masc"
: "fem"; : "fem";
return (nGender === "unisex" && n.gender) return (nGender === "unisex" && n.gender)
@ -112,7 +110,7 @@ function nounGender(n: Noun): T.Gender {
} }
function nounNumber(n: Noun): NounNumber { function nounNumber(n: Noun): NounNumber {
const nNumber = isPluralNounEntry(n.entry) const nNumber = tp.isPluralNounEntry(n.entry)
? "plural" ? "plural"
: "singular"; : "singular";
return nNumber === "plural" return nNumber === "plural"
@ -215,4 +213,3 @@ function getEnglishFromNoun(entry: T.DictionaryEntry, number: NounNumber): strin
} }
return addArticle(e.singular); return addArticle(e.singular);
} }

View File

@ -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[]]);
}

View File

@ -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}` : ""}`);
}

View File

@ -1,7 +0,0 @@
import { renderVP } from "./render-vp";
import { compileVP } from "./compile-vp";
export {
renderVP,
compileVP,
};

View File

@ -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");
}

View File

@ -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)
))
));
}

View File

@ -1,6 +1,9 @@
import shuffle from "./shuffle-array"; import shuffle from "./shuffle-array";
import {
Types as T,
} from "@lingdocs/pashto-inflector";
export const startingWord = (words: Readonly<(NounEntry | AdjectiveEntry)[]>, p: string) => { export const startingWord = (words: Readonly<(T.NounEntry | T.AdjectiveEntry)[]>, p: string) => {
const firstWord = words.find(w => w.p === p); const firstWord = words.find(w => w.p === p);
return [ return [
...firstWord ? [firstWord] : [], ...firstWord ? [firstWord] : [],

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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[],
}

140
src/types/gen-g.d.ts vendored
View File

@ -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

View File

@ -1,3 +1,6 @@
// TODO: I think this is just used for the old equative explorer
// build a new better equative explorer using NP Pickers etc and deprecate all this
type EquativeTense = "present" | "subjunctive" | "habitual" | "past" | "future" | "wouldBe" | "pastSubjunctive"; type EquativeTense = "present" | "subjunctive" | "habitual" | "past" | "future" | "wouldBe" | "pastSubjunctive";
type NounNumber = "singular" | "plural"; type NounNumber = "singular" | "plural";

View File

@ -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];
}

View File

@ -1,10 +1,12 @@
import React, { createContext, useEffect } from "react" import React, { createContext, useEffect } from "react"
import useStickyState from "./useStickyState";
import { import {
AT, AT,
getUser, getUser,
userObjIsEqual, userObjIsEqual,
} from "@lingdocs/lingdocs-main"; } from "@lingdocs/lingdocs-main";
import {
useStickyState,
} from "@lingdocs/pashto-inflector";
import { CronJob } from "cron"; import { CronJob } from "cron";
import { postSavedResults } from "./lib/game-results"; import { postSavedResults } from "./lib/game-results";

View File

@ -1,17 +1,15 @@
import rawWords from "./raw-words"; import rawWords from "./raw-words";
import { import {
isAdjectiveEntry, typePredicates as tp,
isNounEntry, Types as T,
isVerbEntry, } from "@lingdocs/pashto-inflector";
isAdverbEntry,
} from "../lib/type-predicates";
import { categorize } from "../lib/categorize"; import { categorize } from "../lib/categorize";
const words = categorize<Entry, Words>(rawWords, { const words = categorize<T.Entry, T.Words>(rawWords, {
"nouns": isNounEntry, "nouns": tp.isNounEntry,
"adjectives": isAdjectiveEntry, "adjectives": tp.isAdjectiveEntry,
"verbs": isVerbEntry, "verbs": tp.isVerbEntry,
"adverbs": isAdverbEntry, "adverbs": tp.isAdverbEntry,
}); });
export default words; export default words;

View File

@ -1684,14 +1684,15 @@
pbf "^3.2.1" pbf "^3.2.1"
rambda "^6.7.0" rambda "^6.7.0"
"@lingdocs/pashto-inflector@^1.7.0": "@lingdocs/pashto-inflector@^1.8.0":
version "1.7.0" version "1.8.0"
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.7.0.tgz#214b0be0528d3c4017db78255c61b83417d20c35" resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.8.0.tgz#51d4d55cc9a242feb29556136096a4bcfb5474f1"
integrity sha512-/7CrJx1KGO4xPJOkWG9LLEteEdeQ20MA5oX4AI10uQuaZpYULR5oF1J02mwS21wERoMUYjCwMrcZ+G71w/T34A== integrity sha512-EE2F9mEs6px6jpolz75eHoztC4EDJ3090AhKKDaTvun3O5pRSu69yGJJpyAc1sj1SphbQeZSmTdwNAiR43RMBg==
dependencies: dependencies:
classnames "^2.2.6" classnames "^2.2.6"
pbf "^3.2.1" pbf "^3.2.1"
rambda "^6.7.0" rambda "^6.7.0"
react-select "^5.2.2"
"@mdx-js/mdx@^0.15.5": "@mdx-js/mdx@^0.15.5":
version "0.15.7" version "0.15.7"
@ -10486,6 +10487,19 @@ react-select@^5.1.0:
prop-types "^15.6.0" prop-types "^15.6.0"
react-transition-group "^4.3.0" react-transition-group "^4.3.0"
react-select@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.2.tgz#3d5edf0a60f1276fd5f29f9f90a305f0a25a5189"
integrity sha512-miGS2rT1XbFNjduMZT+V73xbJEeMzVkJOz727F6MeAr2hKE0uUSA8Ff7vD44H32x2PD3SRB6OXTY/L+fTV3z9w==
dependencies:
"@babel/runtime" "^7.12.0"
"@emotion/cache" "^11.4.0"
"@emotion/react" "^11.1.1"
"@types/react-transition-group" "^4.4.0"
memoize-one "^5.0.0"
prop-types "^15.6.0"
react-transition-group "^4.3.0"
react-smooth-collapse@^2.1.0: react-smooth-collapse@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/react-smooth-collapse/-/react-smooth-collapse-2.1.0.tgz#64be7af94b61d6d203d9a09af0fba671f9b5983c" resolved "https://registry.yarnpkg.com/react-smooth-collapse/-/react-smooth-collapse-2.1.0.tgz#64be7af94b61d6d203d9a09af0fba671f9b5983c"