diff --git a/package.json b/package.json index ac583ea..55b278a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^5.15.4", "@lingdocs/lingdocs-main": "^0.2.0", - "@lingdocs/pashto-inflector": "^1.7.0", + "@lingdocs/pashto-inflector": "^1.8.0", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", diff --git a/src/components/EntrySelect.tsx b/src/components/EntrySelect.tsx deleted file mode 100644 index 43ad568..0000000 --- a/src/components/EntrySelect.tsx +++ /dev/null @@ -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(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
- -
; - } - 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
- o.value === verb.tense))} - onChange={onTenseSelect} - className="mb-2" - options={tOptions} - {...zIndexProps} - /> - {verb &&
-
- -
- {mode !== "charts" && } -
- -
-
} -
- ; -} - -export default TensePicker; \ No newline at end of file diff --git a/src/components/VerbPicker.tsx b/src/components/VerbPicker.tsx deleted file mode 100644 index 3276532..0000000 --- a/src/components/VerbPicker.tsx +++ /dev/null @@ -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(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
ERROR: Verb version should be select first
; - } - // const [filters, useFilters] = useState({ - // 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
- {!verbLocked &&
-
Verb:
- -
} - {info &&
- setShowRootsAndStems(p => !p)} - hLevel={5} - > - - -
} -
- {verb && verb.changeTransitivity &&
- -
} - {verb && verb.changeVoice &&
- -
} - {verb && verb.changeStatDyn &&
- -
} -
-
; -} - - -export default VerbPicker; \ No newline at end of file diff --git a/src/components/equative-explorer/EquativeDisplay.tsx b/src/components/equative-explorer/EquativeDisplay.tsx index 7b546b2..8828882 100644 --- a/src/components/equative-explorer/EquativeDisplay.tsx +++ b/src/components/equative-explorer/EquativeDisplay.tsx @@ -5,6 +5,7 @@ import { Types as T, personGender, personIsPlural, + typePredicates, } from "@lingdocs/pashto-inflector"; import { ExplorerState, @@ -14,13 +15,13 @@ import { equativeMachine, assembleEquativeOutput, } from "../../lib/equative-machine"; -import { +const { isUnisexNounEntry, isAdjectiveEntry, isVerbEntry, isLocativeAdverbEntry, isNounEntry, -} from "../../lib/type-predicates"; +} = typePredicates; function chooseLength(o: T.SingleOrLengthOpts, length: "short" | "long"): O { return ("long" in o) ? o[length] : o; @@ -54,14 +55,14 @@ function SingleItemDisplay({ state }: { state: ExplorerState }) { } } -function makeComplement(entry: AdjectiveEntry | LocativeAdverbEntry): Compliment { +function makeComplement(entry: T.AdjectiveEntry | T.LocativeAdverbEntry): Compliment { return { type: "compliment", entry, }; } -function makeNounPhrase(entry: NounEntry | UnisexNounEntry | VerbEntry, state: ExplorerState, entity: "subject" | "predicate"): NounPhrase { +function makeNounPhrase(entry: T.NounEntry | T.UnisexNounEntry | T.VerbEntry, state: ExplorerState, entity: "subject" | "predicate"): NounPhrase { if (isVerbEntry(entry)) { return { type: "participle", @@ -76,7 +77,7 @@ function makeNounPhrase(entry: NounEntry | UnisexNounEntry | VerbEntry, state: E }; } -export function makeBlockWPronouns(e: AdjectiveEntry | UnisexNounEntry | LocativeAdverbEntry, tense: EquativeTense, negative: boolean, length?: "short" | "long"): T.SingleOrLengthOpts { +export function makeBlockWPronouns(e: T.AdjectiveEntry | T.UnisexNounEntry | T.LocativeAdverbEntry, tense: EquativeTense, negative: boolean, length?: "short" | "long"): T.SingleOrLengthOpts { // if the output's gonna have long / short forms (if it's past or wouldBe) then recursive call to make the long and short versions if (!length && "long" in assembleEquativeOutput(equativeMachine({ subject: { type: "pronoun", pronounType: "near", person: 0 }, diff --git a/src/components/equative-explorer/explorer-inputs.ts b/src/components/equative-explorer/explorer-inputs.ts index 8a9448c..48dab49 100644 --- a/src/components/equative-explorer/explorer-inputs.ts +++ b/src/components/equative-explorer/explorer-inputs.ts @@ -1,10 +1,10 @@ import { nouns, adjectives, verbs, adverbs } from "../../words/words"; import { - isLocativeAdverbEntry, - isUnisexNounEntry, -} from "../../lib/type-predicates"; + Types as T, + typePredicates as tp, +} from "@lingdocs/pashto-inflector"; -function sort(arr: Readonly): T[] { +function sort(arr: Readonly): T[] { if ("entry" in arr[0]) { return [...arr].sort((a, b) => ( // @ts-ignore @@ -17,15 +17,15 @@ function sort( )); } -const unisexNouns = sort(nouns.filter(x => isUnisexNounEntry(x)) as UnisexNounEntry[]); -const nonUnisexNouns = sort(nouns.filter(x => !isUnisexNounEntry(x)) as (MascNounEntry | FemNounEntry)[]); +const unisexNouns = sort(nouns.filter(x => tp.isUnisexNounEntry(x)) as T.UnisexNounEntry[]); +const nonUnisexNouns = sort(nouns.filter(x => !tp.isUnisexNounEntry(x)) as (T.MascNounEntry | T.FemNounEntry)[]); const inputs = { adjective: sort(adjectives), unisexNoun: unisexNouns, noun: nonUnisexNouns, participle: sort(verbs), - adverb: sort(adverbs.filter(isLocativeAdverbEntry)), + adverb: sort(adverbs.filter(tp.isLocativeAdverbEntry)), }; export const defaultAdjective = inputs.adjective.find(ps => ps.p === "زوړ") || inputs.adjective[0]; diff --git a/src/components/equative-explorer/explorer-reducer.ts b/src/components/equative-explorer/explorer-reducer.ts index d6bbc08..086f39f 100644 --- a/src/components/equative-explorer/explorer-reducer.ts +++ b/src/components/equative-explorer/explorer-reducer.ts @@ -1,9 +1,12 @@ import inputs from "./explorer-inputs"; import { ExplorerState, ExplorerReducerAction } from "./explorer-types"; +import { + Types as T, +} from "@lingdocs/pashto-inflector"; export function reducer(state: ExplorerState, action: ExplorerReducerAction): ExplorerState { if (action.type === "setPredicate") { - const pile = inputs[state.predicate.type] as (UnisexNounEntry | AdjectiveEntry)[]; + const pile = inputs[state.predicate.type] as (T.UnisexNounEntry | T.AdjectiveEntry)[]; const predicate = (pile.find(p => p.ts === action.payload) || pile[0]); return { ...state, diff --git a/src/components/equative-explorer/explorer-selectors.tsx b/src/components/equative-explorer/explorer-selectors.tsx index 7237f2c..49622ed 100644 --- a/src/components/equative-explorer/explorer-selectors.tsx +++ b/src/components/equative-explorer/explorer-selectors.tsx @@ -14,9 +14,17 @@ import { getEnglishWord, Types as T, removeFVarients, + typePredicates, } from "@lingdocs/pashto-inflector"; -import { isAdjectiveEntry, isAdverbEntry, isFemNounEntry, isMascNounEntry, isNounEntry, isPluralNounEntry } from "../../lib/type-predicates"; import Select from "react-select"; +const { + isAdjectiveEntry, + isAdverbEntry, + isFemNounEntry, + isMascNounEntry, + isNounEntry, + isPluralNounEntry, +} = typePredicates; const zIndexProps = { menuPortalTarget: document.body, @@ -83,7 +91,7 @@ export function InputSelector({ state, dispatch, entity }: { } const type = state[entity].type; - const entry: NounEntry | VerbEntry | AdjectiveEntry | LocativeAdverbEntry | undefined = type === "pronouns" + const entry: T.NounEntry | T.VerbEntry | T.AdjectiveEntry | T.LocativeAdverbEntry | undefined = type === "pronouns" ? undefined // @ts-ignore : state[entity][type]; @@ -164,7 +172,7 @@ function GenderAndNumberSelect({ state, dispatch, entity }: { ; } -function makeOption(e: VerbEntry | NounEntry | AdjectiveEntry | LocativeAdverbEntry): { value: string, label: string } { +function makeOption(e: T.VerbEntry | T.NounEntry | T.AdjectiveEntry | T.LocativeAdverbEntry): { value: string, label: string } { const entry = "entry" in e ? e.entry : e; // TODO: THIS IS SUUUPER SKETCH const eng = (isNounEntry(e) || isAdjectiveEntry(e) || isAdverbEntry(e)) diff --git a/src/components/equative-explorer/explorer-types.ts b/src/components/equative-explorer/explorer-types.ts index 23053eb..8b864ee 100644 --- a/src/components/equative-explorer/explorer-types.ts +++ b/src/components/equative-explorer/explorer-types.ts @@ -17,14 +17,14 @@ export type SubjectEntityInfo = EntitiyInfo & { type: SubjectType }; export type PredicateEntityInfo = EntitiyInfo & { type: PredicateType, - adjective: AdjectiveEntry, - adverb: LocativeAdverbEntry, + adjective: import("@lingdocs/pashto-inflector").Types.AdjectiveEntry, + adverb: import("@lingdocs/pashto-inflector").Types.LocativeAdverbEntry, } type EntitiyInfo = { - noun: NounEntry, - participle: VerbEntry, - unisexNoun: UnisexNounEntry, + noun: import("@lingdocs/pashto-inflector").Types.NounEntry, + participle: import("@lingdocs/pashto-inflector").Types.VerbEntry, + unisexNoun: import("@lingdocs/pashto-inflector").Types.UnisexNounEntry, info: { number: NounNumber, gender: T.Gender, diff --git a/src/components/np-picker/NPNounPicker.tsx b/src/components/np-picker/NPNounPicker.tsx deleted file mode 100644 index 6decfdf..0000000 --- a/src/components/np-picker/NPNounPicker.tsx +++ /dev/null @@ -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(undefined); - // const [showFilter, setShowFilter] = useState(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
-
- {props.clearButton} - {/* {(!showFilter && !(noun?.dynamicComplement)) &&
- -
} */} -
- {/* {showFilter &&
-
-
Filter by inflection pattern
-
X
-
- -
} */} - {!(props.noun && props.noun.dynamicComplement) ?
- -
:
- {props.noun &&
-
Included in Dyn. Compound:
-
- - {{ p: props.noun.entry.p, f: props.noun.entry.f }} - -
{props.noun.entry.e}
-
-
} -
} - {props.noun &&
-
- {props.noun.changeGender ? { - if (!props.noun || !props.noun.changeGender) return; - props.onChange(props.noun.changeGender(g)); - }} - /> : props.noun.gender === "masc" ? "Masc." : "Fem."} -
-
- {props.noun.changeNumber ? { - if (!props.noun || !props.noun.changeNumber) return; - props.onChange(props.noun.changeNumber(n)); - }} - /> : props.noun.number === "singular" ? "Sing." : "Plur."} -
-
} -
; -} - -export default NPNounPicker; \ No newline at end of file diff --git a/src/components/np-picker/NPParticiplePicker.tsx b/src/components/np-picker/NPParticiplePicker.tsx deleted file mode 100644 index 465ad5f..0000000 --- a/src/components/np-picker/NPParticiplePicker.tsx +++ /dev/null @@ -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
- {props.clearButton} - - {props.participle &&
-
Masc.
-
Plur.
-
} -
; -} - -export default NPParticiplePicker; \ No newline at end of file diff --git a/src/components/np-picker/NPPicker.tsx b/src/components/np-picker/NPPicker.tsx deleted file mode 100644 index 0913894..0000000 --- a/src/components/np-picker/NPPicker.tsx +++ /dev/null @@ -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(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 = ; - return
- {!npType &&
- {/*
- Choose NP -
*/} - {npTypes.map((npt) =>
- -
)} -
} - {(npType === "pronoun" && props.np?.type === "pronoun") - ? - : npType === "noun" - ? - : npType === "participle" - ? - : null - } -
; -} - -// {(npType && !isDynamicComplement) && } - -export default NPPicker; \ No newline at end of file diff --git a/src/components/np-picker/NPPronounPicker.tsx b/src/components/np-picker/NPPronounPicker.tsx deleted file mode 100644 index bbb047f..0000000 --- a/src/components/np-picker/NPPronounPicker.tsx +++ /dev/null @@ -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
- {clearButton} -
- handlePronounTypeChange(g as "far" | "near")} - /> - -
- - - {pSpec.map((rw, i) => ( - - {rw.map((r, j) => { - const active = (p.row === i && p.col === j) - return ; - })} - - ))} - -
handleClick(i, j)} - className={classNames({ "table-active": active })} - style={{ - backgroundColor: active ? gColors[p.gender] : "inherit", - padding: "0.25rem 0", - }} - > -
- {typeof r === "string" ? r : r[p.gender]} -
-
-
- Masc.
, value: "masc", color: gColors.masc }, - { label:
Fem.
, value: "fem", color: gColors.fem }, - ]} - value={p.gender} - handleChange={(g) => handleGenderChange(g as T.Gender)} - /> -
- ; -}; - -export default NPPronounPicker; \ No newline at end of file diff --git a/src/components/np-picker/picker-tools.tsx b/src/components/np-picker/picker-tools.tsx deleted file mode 100644 index c4e7b60..0000000 --- a/src/components/np-picker/picker-tools.tsx +++ /dev/null @@ -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, - }; - }, - } : {}, - }; -} \ No newline at end of file diff --git a/src/components/phrase-builder/AbbreviationFormSelector.tsx b/src/components/phrase-builder/AbbreviationFormSelector.tsx deleted file mode 100644 index 9a0d5f2..0000000 --- a/src/components/phrase-builder/AbbreviationFormSelector.tsx +++ /dev/null @@ -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
- {/*
Abbreviation Options
*/} - -
-} - -export default AbbreviationFormSelector; \ No newline at end of file diff --git a/src/components/phrase-builder/ChartDisplay.tsx b/src/components/phrase-builder/ChartDisplay.tsx deleted file mode 100644 index 6faeeb1..0000000 --- a/src/components/phrase-builder/ChartDisplay.tsx +++ /dev/null @@ -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
Error conjugating verb
; - } - 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
- -
; -} - -export default ChartDisplay; \ No newline at end of file diff --git a/src/components/phrase-builder/PhraseBuilder.tsx b/src/components/phrase-builder/PhraseBuilder.tsx deleted file mode 100644 index 711dd25..0000000 --- a/src/components/phrase-builder/PhraseBuilder.tsx +++ /dev/null @@ -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(undefined, "subjectNPSelection"); - const [mode, setMode] = useStickyState<"charts" | "phrases">("phrases", "verbExplorerMode"); - const passedVerb = props.verb; - const [verb, setVerb] = useStickyState( - 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
- handleSubjectChange(s, true)} - onChange={setVerb} - opts={textOpts} - /> -
- -
- {(verb && (typeof verb.object === "object") && (verb.isCompound !== "dynamic") && (mode !== "charts")) && -
- -
} -
- {mode !== "charts" && <> -
-
Subject {showRole(VPRendered, "subject")}
- -
- {verb && (verb.object !== "none") &&
-
Object {showRole(VPRendered, "object")}
- {(typeof verb.object === "number") - ?
Unspoken 3rd Pers. Masc. Plur.
- : } -
} - } -
- -
-
- {(verbPhrase && (mode === "phrases")) && - - } - {(verb && (mode === "charts")) && } -
-} - -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 - ? - {(VP.king === member ? kingEmoji : VP.servant === member ? servantEmoji : "")} - - : ""; -} - -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, - } - }; -} \ No newline at end of file diff --git a/src/components/phrase-builder/VPDisplay.tsx b/src/components/phrase-builder/VPDisplay.tsx deleted file mode 100644 index 08f159f..0000000 --- a/src/components/phrase-builder/VPDisplay.tsx +++ /dev/null @@ -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({ removeKing: false, shrinkServant: false }); - const [OSV, setOSV] = useState(false); - const result = compileVP(renderVP(VP), { ...form, OSV }); - return
- {VP.verb.transitivity === "transitive" &&
- setOSV(e.target.checked)} - /> - -
} - - {"long" in result.ps ? -
- {/*
Long Verb:
*/} - - {/*
Short Verb:
*/} - - {result.ps.mini && <> - {/*
Mini Verb:
*/} - - } -
- : - } - {result.e &&
- {result.e.map((e, i) =>
{e}
)} -
} -
-} - -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
- {vs.map((r, i) =>
- {r} -
)} -
; -} - -export default VPDisplay; \ No newline at end of file diff --git a/src/components/phrase-builder/verb-selection.ts b/src/components/phrase-builder/verb-selection.ts deleted file mode 100644 index 730025e..0000000 --- a/src/components/phrase-builder/verb-selection.ts +++ /dev/null @@ -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", - }; - }, - } : {}, - }; -} diff --git a/src/content/inflection/feminine-inflection.mdx b/src/content/inflection/feminine-inflection.mdx index 3b4e4e3..35611fc 100644 --- a/src/content/inflection/feminine-inflection.mdx +++ b/src/content/inflection/feminine-inflection.mdx @@ -22,6 +22,7 @@ import { removeFVarients, InflectionsTable, inflectWord, + typePredicates as tp, } from "@lingdocs/pashto-inflector"; import shuffle from "../../lib/shuffle-array"; import InflectionCarousel from "../../components/InflectionCarousel"; @@ -29,12 +30,8 @@ import { nouns } from "../../words/words"; import Link from "../../components/Link"; import Table from "../../components/Table"; import { startingWord } from "../../lib/starting-word"; -import { - isFemNounEntry, - isPattern6FemEntry, -} from "../../lib/type-predicates"; -export const femNouns = nouns.filter(isFemNounEntry); +export const femNouns = nouns.filter(tp.isFemNounEntry); The 5 basic patterns in the last chapter 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 - + **Note:** This only works with **inanimate nouns**. (ie. words for people like will not inflect like this.) diff --git a/src/content/inflection/inflection-patterns.mdx b/src/content/inflection/inflection-patterns.mdx index eab20c1..2fbc8d2 100644 --- a/src/content/inflection/inflection-patterns.mdx +++ b/src/content/inflection/inflection-patterns.mdx @@ -21,24 +21,15 @@ import { grammarUnits, InflectionsTable, inflectWord, + typePredicates as tp, } from "@lingdocs/pashto-inflector"; -import { - isNounOrVerb, - isPattern1Entry, - isPattern2Entry, - isPattern3Entry, - isPattern4Entry, - isPattern5Entry, - isUnisexNounEntry, -} from "../../lib/type-predicates"; import InflectionCarousel from "../../components/InflectionCarousel"; import { nouns, adjectives } from "../../words/words"; import { startingWord } from "../../lib/starting-word"; import Link from "../../components/Link"; import psmd from "../../lib/psmd"; - export const words = [ - ...nouns.filter(isUnisexNounEntry), + ...nouns.filter(tp.isUnisexNounEntry), ...adjectives, ]; @@ -58,7 +49,7 @@ These words always end in: - **Feminine:** - @@ -93,7 +84,7 @@ In any other kind of sandwich the first inflection would be used. ## 2. Words ending in an unstressed @@ -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. @@ -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 , , endings @@ -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 on the end. diff --git a/src/content/nouns/nouns-gender.mdx b/src/content/nouns/nouns-gender.mdx index 7160dbc..8688646 100644 --- a/src/content/nouns/nouns-gender.mdx +++ b/src/content/nouns/nouns-gender.mdx @@ -6,9 +6,9 @@ import { defaultTextOptions as opts, InlinePs, Examples, + firstVariation, } from "@lingdocs/pashto-inflector"; import genderColors from "../../lib/gender-colors"; -import { firstVariation } from "../../lib/text-tools"; import GenderTable from "../../components/GenderTable"; import Link from "../../components/Link"; export const femColor = genderColors.f; diff --git a/src/content/practice-tools/phrase-builder.mdx b/src/content/practice-tools/phrase-builder.mdx index 6681c05..7e3a1aa 100644 --- a/src/content/practice-tools/phrase-builder.mdx +++ b/src/content/practice-tools/phrase-builder.mdx @@ -3,13 +3,13 @@ title: Phrase Builder fullWidth: true --- -import PhraseBuilder from "../../components/phrase-builder/PhraseBuilder"; import { + VPExplorer, defaultTextOptions, } from "@lingdocs/pashto-inflector"; import { nouns, verbs } from "../../words/words"; - !v.c?.includes("comp.")); +export const verbs = verbsRaw.filter(v => !("complement" in v)); export function InfoCarousel({ items, highlighted, hidePastParticiple }) { return ( diff --git a/src/games/sub-cores/GenderGame.tsx b/src/games/sub-cores/GenderGame.tsx index 849f257..422f0f1 100644 --- a/src/games/sub-cores/GenderGame.tsx +++ b/src/games/sub-cores/GenderGame.tsx @@ -12,44 +12,38 @@ import { pashtoConsonants, inflectWord, isUnisexSet, + typePredicates as tp, + firstVariation, } from "@lingdocs/pashto-inflector"; import { nouns } from "../../words/words"; -import { - firstVariation, -} from "../../lib/text-tools"; -import { - isMascNounEntry, - isFemNounEntry, - isUnisexNounEntry, -} from "../../lib/type-predicates"; import { categorize } from "../../lib/categorize"; const genders: T.Gender[] = ["masc", "fem"]; -const mascNouns = nouns.filter(isMascNounEntry); +const mascNouns = nouns.filter(tp.isMascNounEntry); const femNouns = [ - ...nouns.filter(isFemNounEntry), - ...getFemVersions(mascNouns.filter(isUnisexNounEntry)), + ...nouns.filter(tp.isFemNounEntry), + ...getFemVersions(mascNouns.filter(tp.isUnisexNounEntry)), ]; const types = { - masc: categorize(mascNouns, { consonantMasc: endsWith([{ p: pashtoConsonants }, { p: "و", f: "w" }]), eyMasc: endsWith({ p: "ی", f: "ey" }), uMasc: endsWith({ p: "ه", f: "u" }), yMasc: endsWith([{ p: "ای", f: "aay" }, { p: "وی", f: "ooy" }]), }), - fem: categorize(femNouns, { aaFem: endsWith({ p: "ا", f: "aa" }), eeFem: endsWith({ p: "ي", f: "ee" }), @@ -59,7 +53,7 @@ const types = { }), }; -function getFemVersions(uns: UnisexNounEntry[]): FemNounEntry[] { +function getFemVersions(uns: T.UnisexNounEntry[]): T.FemNounEntry[] { return uns.map((n) => { const infs = inflectWord(n); if (!infs || !infs.inflections) return undefined; @@ -68,18 +62,18 @@ function getFemVersions(uns: UnisexNounEntry[]): FemNounEntry[] { e: n.e, ...infs.inflections.fem[0][0], } as T.DictionaryEntry; - }).filter(n => !!n) as FemNounEntry[]; + }).filter(n => !!n) as T.FemNounEntry[]; } function flatten(o: Record): T[] { return Object.values(o).flat(); } -function nounNotIn(st: NounEntry[]): (n: NounEntry | T.DictionaryEntry) => boolean { +function nounNotIn(st: T.NounEntry[]): (n: T.NounEntry | T.DictionaryEntry) => boolean { return (n: T.DictionaryEntry) => !st.find(x => x.ts === n.ts); } -type CategorySet = Record; +type CategorySet = Record; // for some reason we need to use this CategorySet type here... 🤷‍♂️ const exceptions: Record = { masc: { diff --git a/src/games/sub-cores/UnisexNounGame.tsx b/src/games/sub-cores/UnisexNounGame.tsx index 8fc673f..318a3c4 100644 --- a/src/games/sub-cores/UnisexNounGame.tsx +++ b/src/games/sub-cores/UnisexNounGame.tsx @@ -12,17 +12,13 @@ import { defaultTextOptions as opts, inflectWord, standardizePashto, + firstVariation, + typePredicates as tp, } from "@lingdocs/pashto-inflector"; import { nouns } from "../../words/words"; import { intoPatterns } from "../../lib/categorize"; -import { - firstVariation, -} from "../../lib/text-tools"; -import { - isUnisexNounEntry, -} from "../../lib/type-predicates"; -const unisexNouns = nouns.filter(isUnisexNounEntry); +const unisexNouns = nouns.filter(tp.isUnisexNounEntry); type NType = "pattern1" | "pattern2" | "pattern3" | "pattern4" | "pattern5" | "other"; // TODO: make pattern types as overlay types const types = intoPatterns(unisexNouns); @@ -41,7 +37,7 @@ export default function UnisexNounGame({ id, link }: { id: string, link: string do { type = getRandomFromList(keys); } while (!pool[type].length); - const entry = getRandomFromList( + const entry = getRandomFromList( // @ts-ignore pool[type] ); diff --git a/src/lib/categorize.ts b/src/lib/categorize.ts index 786a7a2..40c42ca 100644 --- a/src/lib/categorize.ts +++ b/src/lib/categorize.ts @@ -1,10 +1,16 @@ import { + typePredicates, + // super weird, need to es-lint disable this this + // eslint-disable-next-line + Types as T, +} from "@lingdocs/pashto-inflector"; +const { isPattern1Entry, isPattern2Entry, isPattern3Entry, isPattern4Entry, isPattern5Entry, -} from "./type-predicates"; +} = typePredicates; /** * sorts a given array of on type into a typed object of arrays of subtypes, based on predicates @@ -75,22 +81,22 @@ export function categorize>( // TODO: uncategorizable words like ایرې - n. f. pl. -- could be pattern 1 or 2 🤷‍♂️ -export function intoPatterns(words: T[]): { - "pattern1": Pattern1Entry[], - "pattern2": Pattern2Entry[], - "pattern3": Pattern3Entry[], - "pattern4": Pattern4Entry[], - "pattern5": Pattern5Entry[], - "other": NonInflecting[], +export function intoPatterns(words: T[]): { + "pattern1": T.Pattern1Entry[], + "pattern2": T.Pattern2Entry[], + "pattern3": T.Pattern3Entry[], + "pattern4": T.Pattern4Entry[], + "pattern5": T.Pattern5Entry[], + "other": T.NonInflecting[], // "pattern6fem": Pattern6FemNoun[], } { - return categorize<(NounEntry | AdjectiveEntry), { - "pattern1": Pattern1Entry[], - "pattern2": Pattern2Entry[], - "pattern3": Pattern3Entry[], - "pattern4": Pattern4Entry[], - "pattern5": Pattern5Entry[], - "other": NonInflecting[], + return categorize<(T.NounEntry | T.AdjectiveEntry), { + "pattern1": T.Pattern1Entry[], + "pattern2": T.Pattern2Entry[], + "pattern3": T.Pattern3Entry[], + "pattern4": T.Pattern4Entry[], + "pattern5": T.Pattern5Entry[], + "other": T.NonInflecting[], // "pattern6fem": Pattern6FemNoun[], }>( words, diff --git a/src/lib/compliment-tools.ts b/src/lib/compliment-tools.ts index 23f4e84..3f72504 100644 --- a/src/lib/compliment-tools.ts +++ b/src/lib/compliment-tools.ts @@ -5,11 +5,9 @@ import { isUnisexSet, personGender, personIsPlural, -} from "@lingdocs/pashto-inflector"; -import { isAdjectiveEntry, isLocativeAdverbEntry } from "./type-predicates"; -import { + typePredicates as tp, psStringFromEntry, -} from "./text-tools"; +} from "@lingdocs/pashto-inflector"; export function evaluateCompliment(c: Compliment, person: T.Person): { ps: T.PsString[], e: string } { const e = getEnglishWord(c.entry); @@ -17,13 +15,13 @@ export function evaluateCompliment(c: Compliment, person: T.Person): { ps: T.PsS console.log(e); throw new Error("error getting english for compliment"); } - if (isLocativeAdverbEntry(c.entry)) { + if (tp.isLocativeAdverbEntry(c.entry)) { return { ps: [psStringFromEntry(c.entry)], e, }; } - if (isAdjectiveEntry(c.entry)) { + if (tp.isAdjectiveEntry(c.entry)) { const infs = inflectWord(c.entry); if (!infs) return { ps: [psStringFromEntry(c.entry)], diff --git a/src/lib/equative-machine.ts b/src/lib/equative-machine.ts index 9c85312..9e1a080 100644 --- a/src/lib/equative-machine.ts +++ b/src/lib/equative-machine.ts @@ -5,6 +5,7 @@ import { getPersonFromVerbForm, concatPsString, removeAccents, + typePredicates as tp, } from "@lingdocs/pashto-inflector"; import { personFromNP, @@ -13,7 +14,6 @@ import { import { evaluateCompliment, } from "./compliment-tools"; -import { isPluralNounEntry } from "./type-predicates"; // Equative Rules // - An equative equates a SUBJECT: Noun Phrase and a PREDICATE: Noun Phrase | Compliment @@ -84,7 +84,7 @@ function makeEquative(e: EquativeClause) { ? "past" : e.tense; const subjP = personFromNP(e.subject); - const englishPerson = (e.subject.type === "participle" || (e.subject.type === "noun" && isPluralNounEntry(e.subject.entry))) + const englishPerson = (e.subject.type === "participle" || (e.subject.type === "noun" && tp.isPluralNounEntry(e.subject.entry))) ? T.Person.ThirdSingMale : subjP const pashtoPerson = (e.subject.type === "pronoun") diff --git a/src/lib/np-tools.ts b/src/lib/np-tools.ts index 1acfb7c..5290e44 100644 --- a/src/lib/np-tools.ts +++ b/src/lib/np-tools.ts @@ -1,4 +1,3 @@ -import { isMascNounEntry, isPluralNounEntry, isUnisexNounEntry } from "./type-predicates"; import { Types as T, getEnglishWord, @@ -6,16 +5,15 @@ import { getVerbBlockPosFromPerson, grammarUnits, inflectWord, -} from "@lingdocs/pashto-inflector"; -import { psStringFromEntry, -} from "./text-tools"; + typePredicates as tp, +} from "@lingdocs/pashto-inflector"; function getRandPers(): T.Person { return Math.floor(Math.random() * 12); } -export function randomPerson(a?: { prev?: T.Person, counterPart?: VerbObject | NPSelection }) { +export function randomPerson(a?: { prev?: T.Person, counterPart?: T.VerbObject | T.NPSelection }) { // no restrictions, just get any person if (!a) { return getRandPers(); @@ -99,9 +97,9 @@ export function evaluateNP(np: NounPhrase): { ps: T.PsString[], e: string } { } function nounGender(n: Noun): T.Gender { - const nGender = isUnisexNounEntry(n.entry) + const nGender = tp.isUnisexNounEntry(n.entry) ? "unisex" - : isMascNounEntry(n.entry) + : tp.isMascNounEntry(n.entry) ? "masc" : "fem"; return (nGender === "unisex" && n.gender) @@ -112,7 +110,7 @@ function nounGender(n: Noun): T.Gender { } function nounNumber(n: Noun): NounNumber { - const nNumber = isPluralNounEntry(n.entry) + const nNumber = tp.isPluralNounEntry(n.entry) ? "plural" : "singular"; return nNumber === "plural" @@ -215,4 +213,3 @@ function getEnglishFromNoun(entry: T.DictionaryEntry, number: NounNumber): strin } return addArticle(e.singular); } - diff --git a/src/lib/phrase-building/compile-vp.ts b/src/lib/phrase-building/compile-vp.ts deleted file mode 100644 index 4ea18b3..0000000 --- a/src/lib/phrase-building/compile-vp.ts +++ /dev/null @@ -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, 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, 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, - }, - VP: VPRendered, -} -function compilePs({ NPs, kids, verb: { head, rest }, VP }: CompilePsInput): T.SingleOrLengthOpts { - 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): 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[] { - 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[]]); -} diff --git a/src/lib/phrase-building/english-vp-rendering.ts b/src/lib/phrase-building/english-vp-rendering.ts deleted file mode 100644 index 93c99a0..0000000 --- a/src/lib/phrase-building/english-vp-rendering.ts +++ /dev/null @@ -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}` : ""}`); -} diff --git a/src/lib/phrase-building/index.ts b/src/lib/phrase-building/index.ts deleted file mode 100644 index 88b75b6..0000000 --- a/src/lib/phrase-building/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { renderVP } from "./render-vp"; -import { compileVP } from "./compile-vp"; - -export { - renderVP, - compileVP, -}; \ No newline at end of file diff --git a/src/lib/phrase-building/render-vp.ts b/src/lib/phrase-building/render-vp.ts deleted file mode 100644 index 8d363b5..0000000 --- a/src/lib/phrase-building/render-vp.ts +++ /dev/null @@ -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 -export function renderNPSelection(NP: NPSelection | ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "object"): Rendered | T.Person.ThirdPlurMale | "none"; -export function renderNPSelection(NP: NPSelection | ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "subject" | "object"): Rendered | 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 { - 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 { - 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 { - 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, - }, - 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, person: T.Person): T.SingleOrLengthOpts { - 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): { head: T.PsString | undefined, rest: T.SingleOrLengthOpts }; -function getHeadAndRest(head: T.PsString, rest: T.SingleOrLengthOpts): { head: T.PsString | undefined, rest: T.SingleOrLengthOpts } { - 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(f: { - mascSing: T.SingleOrLengthOpts; - mascPlur: T.SingleOrLengthOpts; - femSing: T.SingleOrLengthOpts; - femPlur: T.SingleOrLengthOpts; -} | T.SingleOrLengthOpts, objectPerson: T.Person | undefined, kingPerson: T.Person): T.SingleOrLengthOpts { - 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"); -} diff --git a/src/lib/phrase-building/vp-tools.ts b/src/lib/phrase-building/vp-tools.ts deleted file mode 100644 index cfde521..0000000 --- a/src/lib/phrase-building/vp-tools.ts +++ /dev/null @@ -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) - )) - )); -} \ No newline at end of file diff --git a/src/lib/starting-word.ts b/src/lib/starting-word.ts index 23e8d8d..0a3cddc 100644 --- a/src/lib/starting-word.ts +++ b/src/lib/starting-word.ts @@ -1,6 +1,9 @@ import shuffle from "./shuffle-array"; +import { + Types as T, +} from "@lingdocs/pashto-inflector"; -export const startingWord = (words: Readonly<(NounEntry | AdjectiveEntry)[]>, p: string) => { +export const startingWord = (words: Readonly<(T.NounEntry | T.AdjectiveEntry)[]>, p: string) => { const firstWord = words.find(w => w.p === p); return [ ...firstWord ? [firstWord] : [], diff --git a/src/lib/text-tools.ts b/src/lib/text-tools.ts deleted file mode 100644 index f469703..0000000 --- a/src/lib/text-tools.ts +++ /dev/null @@ -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(x: T.SingleOrLengthOpts): U { - if ("long" in x) { - return x.long; - } - return x; -} - -export function capitalizeFirstLetter(string: string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} \ No newline at end of file diff --git a/src/lib/type-predicates.ts b/src/lib/type-predicates.ts deleted file mode 100644 index 547974b..0000000 --- a/src/lib/type-predicates.ts +++ /dev/null @@ -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(e: T): e is Pattern1Entry { - 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(e: T): e is Pattern2Entry { - 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(e: T): e is Pattern3Entry { - 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(e: T): e is Pattern4Entry { - 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(e: T): e is Pattern5Entry { - 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 { - if (!isFemNounEntry(e)) return false; - if (e.c.includes("anim.")) return false; - return e.p.slice(-1) === "ي"; -} - -export function isPluralNounEntry(e: U): e is PluralNounEntry { - return e.c.includes("pl."); -} - -export function isSingularEntry(e: U): e is SingularEntry { - return !isPluralNounEntry(e); -} - -export function isArrayOneOrMore(a: U[]): a is T.ArrayOneOrMore { - return a.length > 0; -} diff --git a/src/types/entry-types.d.ts b/src/types/entry-types.d.ts deleted file mode 100644 index 2551a8b..0000000 --- a/src/types/entry-types.d.ts +++ /dev/null @@ -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 & { __brand7: "a singular noun - as opposed to an always plural noun" }; -type PluralNounEntry = T & { __brand7: "a noun that is always plural" }; - -type Pattern1Entry = T & { __brand3: "basic inflection pattern" }; -type Pattern2Entry = T & { __brand3: "ending in unstressed ی pattern" }; -type Pattern3Entry = T & { __brand3: "ending in stressed ی pattern" }; -type Pattern4Entry = T & { __brand3: "Pashtoon pattern" }; -type Pattern5Entry = T & { __brand3: "short squish pattern" }; -type Pattern6FemEntry = T & { __brand3: "non anim. ending in ي" }; -type NonInflecting = T & { __brand3: "non-inflecting" }; - -type Entry = NounEntry | AdjectiveEntry | AdverbEntry | VerbEntry; - -type Words = { - nouns: NounEntry[], - adjectives: AdjectiveEntry[], - verbs: VerbEntry[], - adverbs: AdverbEntry[], -} diff --git a/src/types/gen-g.d.ts b/src/types/gen-g.d.ts deleted file mode 100644 index 5b5d216..0000000 --- a/src/types/gen-g.d.ts +++ /dev/null @@ -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, - verb: Exclude, -}; - -// TODO: make this Rendered with recursive Rendered<> -type VPRendered = { - type: "VPRendered", - king: "subject" | "object", - servant: "subject" | "object" | undefined, - isPast: boolean, - isTransitive: boolean, - isCompound: "stative" | "dynamic" | false, - subject: Rendered, - object: Rendered | 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 & { - 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 extends Record ? (Omit & Record) : T; - -type FormVersion = { removeKing: boolean, shrinkServant: boolean }; - -type Rendered = ReplaceKey< - Omit, - "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 - - diff --git a/src/types/gramm-types.d.ts b/src/types/gramm-types.d.ts index 24e4e4d..d57bc7a 100644 --- a/src/types/gramm-types.d.ts +++ b/src/types/gramm-types.d.ts @@ -1,3 +1,6 @@ +// TODO: I think this is just used for the old equative explorer +// build a new better equative explorer using NP Pickers etc and deprecate all this + type EquativeTense = "present" | "subjunctive" | "habitual" | "past" | "future" | "wouldBe" | "pastSubjunctive"; type NounNumber = "singular" | "plural"; diff --git a/src/useStickyState.ts b/src/useStickyState.ts deleted file mode 100644 index ce824e3..0000000 --- a/src/useStickyState.ts +++ /dev/null @@ -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( - defaultValue: T | ((old: T | undefined) => T), - key: string -): [ - value: T, - setValue: React.Dispatch>, -] { - const [value, setValue] = useState(() => { - 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]; -} \ No newline at end of file diff --git a/src/user-context.tsx b/src/user-context.tsx index c42e74f..437ccb7 100644 --- a/src/user-context.tsx +++ b/src/user-context.tsx @@ -1,10 +1,12 @@ import React, { createContext, useEffect } from "react" -import useStickyState from "./useStickyState"; import { AT, getUser, userObjIsEqual, } from "@lingdocs/lingdocs-main"; +import { + useStickyState, +} from "@lingdocs/pashto-inflector"; import { CronJob } from "cron"; import { postSavedResults } from "./lib/game-results"; diff --git a/src/words/words.ts b/src/words/words.ts index 014490d..c937a00 100644 --- a/src/words/words.ts +++ b/src/words/words.ts @@ -1,17 +1,15 @@ import rawWords from "./raw-words"; import { - isAdjectiveEntry, - isNounEntry, - isVerbEntry, - isAdverbEntry, -} from "../lib/type-predicates"; + typePredicates as tp, + Types as T, +} from "@lingdocs/pashto-inflector"; import { categorize } from "../lib/categorize"; -const words = categorize(rawWords, { - "nouns": isNounEntry, - "adjectives": isAdjectiveEntry, - "verbs": isVerbEntry, - "adverbs": isAdverbEntry, +const words = categorize(rawWords, { + "nouns": tp.isNounEntry, + "adjectives": tp.isAdjectiveEntry, + "verbs": tp.isVerbEntry, + "adverbs": tp.isAdverbEntry, }); export default words; diff --git a/yarn.lock b/yarn.lock index 89a8967..059a31c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1684,14 +1684,15 @@ pbf "^3.2.1" rambda "^6.7.0" -"@lingdocs/pashto-inflector@^1.7.0": - version "1.7.0" - resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.7.0.tgz#214b0be0528d3c4017db78255c61b83417d20c35" - integrity sha512-/7CrJx1KGO4xPJOkWG9LLEteEdeQ20MA5oX4AI10uQuaZpYULR5oF1J02mwS21wERoMUYjCwMrcZ+G71w/T34A== +"@lingdocs/pashto-inflector@^1.8.0": + version "1.8.0" + resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.8.0.tgz#51d4d55cc9a242feb29556136096a4bcfb5474f1" + integrity sha512-EE2F9mEs6px6jpolz75eHoztC4EDJ3090AhKKDaTvun3O5pRSu69yGJJpyAc1sj1SphbQeZSmTdwNAiR43RMBg== dependencies: classnames "^2.2.6" pbf "^3.2.1" rambda "^6.7.0" + react-select "^5.2.2" "@mdx-js/mdx@^0.15.5": version "0.15.7" @@ -10486,6 +10487,19 @@ react-select@^5.1.0: prop-types "^15.6.0" react-transition-group "^4.3.0" +react-select@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.2.tgz#3d5edf0a60f1276fd5f29f9f90a305f0a25a5189" + integrity sha512-miGS2rT1XbFNjduMZT+V73xbJEeMzVkJOz727F6MeAr2hKE0uUSA8Ff7vD44H32x2PD3SRB6OXTY/L+fTV3z9w== + dependencies: + "@babel/runtime" "^7.12.0" + "@emotion/cache" "^11.4.0" + "@emotion/react" "^11.1.1" + "@types/react-transition-group" "^4.4.0" + memoize-one "^5.0.0" + prop-types "^15.6.0" + react-transition-group "^4.3.0" + react-smooth-collapse@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-smooth-collapse/-/react-smooth-collapse-2.1.0.tgz#64be7af94b61d6d203d9a09af0fba671f9b5983c"