diff --git a/package.json b/package.json index bd9f1a0..8c7adc1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lingdocs/pashto-inflector", - "version": "2.9.8", + "version": "2.9.9", "author": "lingdocs.com", "description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations", "homepage": "https://verbs.lingdocs.com", diff --git a/src/components/ep-explorer/EPTextDisplay.tsx b/src/components/CompiledPTextDisplay.tsx similarity index 81% rename from src/components/ep-explorer/EPTextDisplay.tsx rename to src/components/CompiledPTextDisplay.tsx index 61cc458..d3d0de6 100644 --- a/src/components/ep-explorer/EPTextDisplay.tsx +++ b/src/components/CompiledPTextDisplay.tsx @@ -1,8 +1,8 @@ -import { getShort } from "../../lib/p-text-helpers"; -import * as T from "../../types"; -import Examples from "../Examples"; +import { getShort } from "../lib/p-text-helpers"; +import * as T from "../types"; +import Examples from "./Examples"; -function EPTextDisplay({ compiled, opts, justify, onlyOne }: { +function CompiledPTextDisplay({ compiled, opts, justify, onlyOne }: { compiled: { ps: T.SingleOrLengthOpts; e?: string[] | undefined; @@ -30,4 +30,4 @@ function EPTextDisplay({ compiled, opts, justify, onlyOne }: { ; } -export default EPTextDisplay; \ No newline at end of file +export default CompiledPTextDisplay; \ No newline at end of file diff --git a/src/components/ConjugationViewer.tsx b/src/components/ConjugationViewer.tsx deleted file mode 100644 index 5ce44db..0000000 --- a/src/components/ConjugationViewer.tsx +++ /dev/null @@ -1,405 +0,0 @@ -/** - * Copyright (c) 2021 lingdocs.com - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import { useEffect, useReducer } from "react"; -import VerbInfo from "./verb-info/VerbInfo"; -import VerbFormDisplay from "./VerbFormDisplay"; -import ButtonSelect from "./ButtonSelect"; -import Hider from "./Hider"; -import { getForms } from "../lib/conjugation-forms"; -import { conjugateVerb } from "../lib/verb-conjugation"; -import PersonSelection from "./PersonSelection"; -import { - incrementPerson, - parseEc, -} from "../lib/misc-helpers"; -import * as T from "../types"; -import { - randomPerson, - isInvalidSubjObjCombo, -} from "../lib/np-tools"; - -const VerbChoiceWarning = () => ( - <> -
- This verb can be used in different ways! -
-

Choose which way to use it:

- -); - -const stateLocalStorageName = "explorerState6"; - -// remember to increment the stateLocalStorageName whenever changing -// the State type -type State = { - mode: "chart" | "sentence"; - subject: T.Person, - object: T.Person, - negative: boolean, - compoundTypeSelected: "stative" | "dynamic"; - transitivitySelected: "transitive" | "grammatically transitive"; - compoundComplementVersionSelected: "sing" | "plur"; - showingStemsAndRoots: boolean; - formsOpened: string[]; - showingFormInfo: boolean; -} - -type Action = { - type: "choose compound type", - payload: "stative" | "dynamic", -} | { - type: "set forms opened", - payload: string, -} | { - type: "set compound complement version", - payload: "sing" | "plur", -} | { - type: "toggle showingStemsAndRoots", -} | { - type: "setState", - payload: State, -} | { - type: "setMode", - payload: "chart" | "sentence", -} | { - type: "setPerson", - payload: { setting: "subject" | "object", person: T.Person }, -} | { - type: "randomPerson", - payload: "subject" | "object", -} | { - type: "setShowingFormInfo", - payload: boolean, -} | { - type: "setTransitivitySelected", - payload: "transitive" | "grammatically transitive", -} | { - type: "setNegative", - payload: boolean, -}; - -function oppositeRole(x: "subject" | "object"): "subject" | "object" { - return x === "subject" ? "object" : "subject"; -} - -function reducer(state: State, action: Action): State { - function applyFormOpen( - payload: string, - formsOpened: string[], - ): string[] { - if (formsOpened.includes(payload)) { - return formsOpened.filter((f) => f !== payload); - } - return [...formsOpened, payload]; - } - function setPerson({ setting, person }: { setting: "subject" | "object", person: T.Person }): State { - let newPerson = person; - let otherPerson = state[oppositeRole(setting)]; - let otherSetting = oppositeRole(setting); - while (isInvalidSubjObjCombo(newPerson, otherPerson)) { - otherPerson = incrementPerson(otherPerson); - } - return { ...state, [setting]: newPerson, [otherSetting]: otherPerson }; - } - - switch(action.type) { - case "choose compound type": - return { ...state, compoundTypeSelected: action.payload }; - case "set forms opened": - return { - ...state, - formsOpened: applyFormOpen(action.payload, state.formsOpened), - }; - case "set compound complement version": - return { ...state, compoundComplementVersionSelected: action.payload }; - case "toggle showingStemsAndRoots": - return { ...state, showingStemsAndRoots: !state.showingStemsAndRoots }; - case "setState": - return action.payload; - case "setMode": - return { ...state, mode: action.payload }; - case "setPerson": - return setPerson(action.payload); - case "randomPerson": - return { - ...state, - [action.payload]: randomPerson({ - prev: state[action.payload === "subject" ? "object" : "subject"] - }), - }; - case "setShowingFormInfo": - return { - ...state, - showingFormInfo: action.payload, - }; - case "setTransitivitySelected": - return { - ...state, - transitivitySelected: action.payload, - }; - case "setNegative": - return { - ...state, - negative: action.payload, - }; - default: - throw new Error(); - } -} - -const initialState: State = { - subject: 0, - object: 2, - negative: false, - compoundTypeSelected: "stative", - transitivitySelected: "transitive", - mode: "chart", - compoundComplementVersionSelected: "plur", - showingStemsAndRoots: false, - showingFormInfo: false, - formsOpened: [], -}; - -function ConjugationViewer({ entry, complement, textOptions, showOnly, highlightInRootsAndStems, hidePastParticiple, sentenceLevel }: { - entry: T.DictionaryEntry, - complement?: T.DictionaryEntry, - textOptions: T.TextOptions, - showOnly?: string | string[], - highlightInRootsAndStems?: T.RootsOrStemsToHighlight, - hidePastParticiple?: boolean, - sentenceLevel?: "easy" | "medium" | "hard", -}) { - const [state, dispatch] = useReducer(reducer, initialState); - useEffect(() => { - const stateRaw = localStorage.getItem(stateLocalStorageName); - if (stateRaw) { - try { - const payload = JSON.parse(stateRaw) as State; - dispatch({ type: "setState", payload }); - } catch (e) { - console.error("error parsing saved state JSON", e); - } - } - }, []); - useEffect(() => { - localStorage.setItem(stateLocalStorageName, JSON.stringify(state)); - }); - const conjugation = (() => { - if (!(entry.c && entry.c.slice(0, 2) === "v.")) return undefined; - try { - return conjugateVerb(entry, complement); - } catch(e) { - console.error("conjugation error", e); - return undefined; - } - })(); - if (conjugation === undefined) { - // don't show the conjugation viewer if the verb can't be conjugated - return null; - } - - const verbConj1 = ("dynamic" in conjugation) - ? conjugation[state.compoundTypeSelected] - : ("transitive" in conjugation) - ? conjugation[state.transitivitySelected === "transitive" ? "transitive" : "grammaticallyTransitive"] - : conjugation; - const verbConj = (verbConj1.singularForm && state.compoundComplementVersionSelected === "sing") - ? verbConj1.singularForm - : verbConj1; - const englishConjugation: T.EnglishVerbConjugation | undefined = entry.ec ? { - ec: parseEc(entry.ec), - ep: entry.ep, - } : undefined; - - const limitTo = !showOnly - ? undefined - : Array.isArray(showOnly) - ? showOnly - : [showOnly]; - const forms = getForms({ - conj: verbConj, - filterFunc: [ - ...limitTo ? [(f: T.DisplayForm): boolean => limitTo.includes(f.label) - ] : [], - ], - mode: state.mode, - subject: state.subject, - object: state.object, - negative: state.negative, - sentenceLevel, - englishConjugation, - }); - const sentencesAvailable = forms.some((form) => "sentence" in form); - return
- {"transitive" in conjugation &&
- -
- dispatch({ type: "setTransitivitySelected", payload: p })} - /> -
-
} - {"dynamic" in conjugation &&
- -
- dispatch({ type: "choose compound type", payload: p })} - /> -
-
} - {verbConj1.singularForm &&
- -
- dispatch({ type: "set compound complement version", payload: p })} - /> -
-
} - dispatch({ type: "toggle showingStemsAndRoots" })} - hidePastParticiple={hidePastParticiple} - hideTypeInfo={!!limitTo} - /> -
- {sentencesAvailable &&
- dispatch({ type: "setMode", payload: p })} - /> -
} - {!limitTo && <> -
- { - dispatch({ type: "setShowingFormInfo", payload: e.target.checked }) - }} - /> - -
- } -
- {(state.mode === "sentence" && sentencesAvailable) && -
- dispatch({ - type: "setPerson", payload, - })} - handleRandom={(payload: "subject" | "object") => dispatch({ - type: "randomPerson", payload, - })} - textOptions={textOptions} - /> -
- dispatch({ type: "setNegative", payload: p === "true" })} - /> -
-
- } - dispatch({ type: "set forms opened", payload })} - verbConj={verbConj} - textOptions={textOptions} - /> -
; -} - -function FormsDisplay({ forms, state, handleChange, textOptions, verbConj }: { - verbConj: T.VerbConjugation, - forms: T.DisplayFormItem[], - state: State, - handleChange: (p: string) => void, - textOptions: T.TextOptions, -}) { - function drawLevel(forms: T.DisplayFormItem[], level: number) { - return
- {forms.map((f, i) => { - if ("content" in f && f.content.length === 0) { - return null; - } - const showDividerLine = (item: T.DisplayFormItem, index: number): boolean => { - return (index !== 0) && ("content" in item || !item.aspect || (item.aspect === "imperfective")); - }; - return
- {showDividerLine(f, i) &&
} - handleChange(f.label)} - ignore={forms.length === 1} - > - {"content" in f ? - drawLevel(f.content, level + 1) - : - - } - -
; - })} -
- } - return
- {drawLevel(forms, 0)} -
; -} - -export default ConjugationViewer; diff --git a/src/components/DisplayModeSelect.tsx b/src/components/DisplayModeSelect.tsx new file mode 100644 index 0000000..2a044a1 --- /dev/null +++ b/src/components/DisplayModeSelect.tsx @@ -0,0 +1,23 @@ +export type Mode = "text" | "blocks"; + +function ModeSelect({ value, onChange }: { value: Mode, onChange: (m: Mode) => void }) { + return
+ {value === "text" ?
onChange("blocks")}> + +
:
onChange("text")}> + +
} +
; +} + +export function ScriptSelect({ value, onChange }: { value: "p" | "f", onChange: (m: "p" | "f") => void }) { + return ; +} + +export default ModeSelect; \ No newline at end of file diff --git a/src/components/RenderedBlocksDisplay.tsx b/src/components/RenderedBlocksDisplay.tsx new file mode 100644 index 0000000..afdb076 --- /dev/null +++ b/src/components/RenderedBlocksDisplay.tsx @@ -0,0 +1,55 @@ +import { useState } from "react"; +import { filterForVisibleBlocksEP, filterForVisibleBlocksVP } from "../lib/phrase-building/compile"; +import * as T from "../types"; +import Block from "./blocks/Block"; +import KidDisplay from "./blocks/KidDisplay"; + +function RenderedBlocksDisplay({ opts, rendered, justify, script }: { + script: "p" | "f", + opts: T.TextOptions, + rendered: T.EPRendered | T.VPRendered, + justify?: "left" | "right" | "center", +}) { + const [variation, setVariation] = useState(0); + const blocksWVars = ("omitSubject" in rendered) + ? filterForVisibleBlocksEP(rendered.blocks, rendered.omitSubject) + : filterForVisibleBlocksVP(rendered.blocks, rendered.form, rendered.king); + const king = "king" in rendered ? rendered.king : undefined; + const blocks = blocksWVars[variation]; + function handleVariationChange() { + setVariation(ov => ((ov + 1) % blocksWVars.length)); + } + return
+
+
+ +
+ + {blocks.slice(1).map((block) => ( +
+ +
+ ))} +
+ {blocksWVars.length > 1 && } +
+
+
+} + +function KidsSection({ opts, kids, script }: { + opts: T.TextOptions, + kids: T.Kid[], + script: "p" | "f", +}) { + return kids.length > 0 ?
+
+ {kids.map(kid => ( + + ))} +
+
kids
+
: null; +} + +export default RenderedBlocksDisplay; \ No newline at end of file diff --git a/src/components/blocks/Block.tsx b/src/components/blocks/Block.tsx index 577e09f..4ae4782 100644 --- a/src/components/blocks/Block.tsx +++ b/src/components/blocks/Block.tsx @@ -4,89 +4,245 @@ import { getEnglishFromRendered, } from "../../lib/phrase-building/np-tools"; import { getEnglishPersonInfo } from "../../library"; +import { useState } from "react"; +import { getLength } from "../../lib/p-text-helpers"; +import { roleIcon } from "../vp-explorer/VPExplorerExplanationModal"; +import { negativeParticle } from "../../lib/grammar-units"; -function Block({ opts, block }: { +function Block({ opts, block, king, script }: { opts: T.TextOptions, block: T.Block, + king?: "subject" | "object" | undefined, + script: "p" | "f"; }) { if ("equative" in block) { - return ; + return ; } if (block.type === "AP") { const english = getEnglishFromRendered(block); - return {block} + return {block} } if (block.type === "subjectSelection") { - return + const role = king === "subject" ? "king" : king === "object" ? "servant" : undefined; + return } if (block.type === "predicateSelection") { const english = getEnglishFromRendered(block.selection); return
Predicate
{block.selection.type === "EQComp" - ? - : {block.selection}} + ? + : {block.selection}}
} - if (block.type === "nu") { - return + if (block.type === "negative") { + return + } + if (block.type === "perfectiveHead") { + return + } + if (block.type === "verbComplement") { + return ; + } + if (block.type === "verb") { + return ; + } + if (block.type === "objectSelection") { + const role = king === "object" ? "king" : king === "subject" ? "servant" : undefined; + return ; + } + if (block.type === "perfectParticipleBlock") { + return ; + } + if (block.type === "perfectEquativeBlock") { + return ; + } + if (block.type === "modalVerbBlock") { + return ; + } + if (block.type === "modalVerbKedulPart") { + return } return null; } export default Block; -function NUBlock({ opts }: { +function Border({ children, extraClassName, padding }: { children: JSX.Element | JSX.Element[] | string, extraClassName?: string, padding?: string }) { + return
+ <>{children} +
+} + +function VerbSBlock({ opts, v, script }: { opts: T.TextOptions, + script: "p" | "f", + v: T.VerbRenderedBlock["block"] | T.PerfectParticipleBlock, +}) { + const [length, setLength] = useState("long"); + function changeLength() { + setLength(o => ( + o === "long" + ? "short" + : o === "short" && "mini" in v.ps + ? "mini" + : "long" + )); + } + return
+ {"long" in v.ps &&
{length}
} + + {getLength(v.ps, length)[0][script]} + +
{v.type === "perfectParticipleBlock" ? "Past Partic." : "Verb"}
+ {getEnglishPersonInfo(v.person, "short")} +
+} + +function ModalVerbBlock({ opts, v, script }: { + opts: T.TextOptions, + script: "p" | "f", + v: T.ModalVerbBlock, +}) { + const [length, setLength] = useState("long"); + function changeLength() { + setLength(o => ( + o === "long" + ? "short" + : "long" + )); + } + return
+ {"long" in v.ps &&
{length}
} + + {getLength(v.ps, length)[0][script]} + +
Verb
+ Modal +
+} + +function PerfHeadBlock({ opts, ps, script }: { + opts: T.TextOptions, + ps: T.PsString, + script: "p" | "f", + }) { return
-
- nu -
+ + {ps[script]} + +
perf. head
+ {'\u00A0'} +
; +} + +function VCompBlock({ opts, comp, script }: { + opts: T.TextOptions, + comp: T.VerbComplementBlock["complement"], + script: "p" | "f", +}) { + return
+ + {comp[script]} + +
Complement
+ {'\u00A0'} +
; +} + +function ModalAuxBlock({ opts, aux, script }: { + opts: T.TextOptions, + aux: T.ModalVerbKedulPart, + script: "p" | "f", + +}) { + return
+ + {aux.ps[0][script]} + +
Modal Aux
+ {getEnglishPersonInfo(aux.verb.block.person, "short")} +
; +} + +function NegBlock({ opts, imperative, script }: { + opts: T.TextOptions, + imperative: boolean, + script: "p" | "f", +}) { + return
+ + {negativeParticle[imperative ? "imperative" : "nonImperative"][script]} +
Neg.
- not + {imperative ? "don't" : "not"}
; } -function EquativeBlock({ opts, eq }: { +function EquativeBlock({ opts, eq, script }: { opts: T.TextOptions, - eq: T.EquativeRendered, + eq: T.EquativeRendered | T.PerfectEquativeBlock, + script: "p" | "f", }) { + const [length, setLength] = useState("long"); + function changeLength() { + setLength(o => ( + o === "long" + ? "short" + : o === "short" && "mini" in eq.ps + ? "mini" + : "long" + )); + } return
-
- {"short" in eq.ps ? eq.ps.short[0].f : eq.ps[0].f} -
+ {"long" in eq.ps &&
{length}
} + + {getLength(eq.ps, length)[0][script]} +
Equative
- {"="} + {getEnglishPersonInfo(eq.person, "short")}
; } -function SubjectBlock({ opts, np }: { +function SubjectBlock({ opts, np, role, script }: { opts: T.TextOptions, np: T.Rendered, + role: "king" | "servant" | undefined, + script: "p" | "f", }) { const english = getEnglishFromRendered(np); return
-
Subject
- {np} +
Subject{role ? roleIcon[role] : ""}
+ {np}
; } -function EqCompBlock({ opts, comp }: { +function ObjectBlock({ opts, obj, role, script }: { + opts: T.TextOptions, + obj: T.Rendered["selection"], + role: "king" | "servant" | undefined, + script: "p" | "f", +}) { + if (typeof obj !== "object") { + return null; + } + const english = getEnglishFromRendered(obj); + return
+
Object{role ? roleIcon[role] : ""}
+ {obj} +
; +} + +function EqCompBlock({ opts, comp, script }: { + script: "p" | "f", opts: T.TextOptions, comp: T.Rendered, }) { @@ -95,16 +251,9 @@ function EqCompBlock({ opts, comp }: { adj: T.Rendered, }) { return
-
- {adj.ps[0].f} -
+ + {adj.ps[0][script]} +
Adj.
{adj.e}
; @@ -115,16 +264,9 @@ function EqCompBlock({ opts, comp }: { adv: T.Rendered, }) { return
-
- {adv.ps[0].f} -
+ + {adv.ps[0][script]} +
Loc. Adv.
{adv.e}
; @@ -137,89 +279,78 @@ function EqCompBlock({ opts, comp }: { : comp.type === "loc. adv." ? :
- +
Sandwich
{comp.e}
} ; } -export function APBlock({ opts, children, english }: { +export function APBlock({ opts, children, english, script }: { opts: T.TextOptions, children: T.Rendered, english?: string, + script: "p" | "f", }) { const ap = children; if (ap.selection.type === "adverb") { return
-
- {ap.selection.ps[0].f} -
+ + {ap.selection.ps[0][script]} +
AP
{english}
; } return
- +
AP
{english}
; } -function Sandwich({ opts, sandwich }: { +function Sandwich({ opts, sandwich, script }: { opts: T.TextOptions, sandwich: T.Rendered>, + script: "p" | "f", }) { return
Sandwich 🥪
-
-
- {sandwich.inside.selection.type !== "pronoun" ? sandwich.inside.selection.possesor : undefined} + +
+ {sandwich.inside.selection.type !== "pronoun" ? sandwich.inside.selection.possesor : undefined}
{sandwich.before ? sandwich.before.f : ""}
- {sandwich.inside} + {sandwich.inside}
{sandwich.after ? sandwich.after.f : ""}
-
+
; } -export function NPBlock({ opts, children, inside, english }: { +export function NPBlock({ opts, children, inside, english, script }: { opts: T.TextOptions, children: T.Rendered, inside?: boolean, english?: string, + script: "p" | "f", }) { const np = children; const hasPossesor = !!(np.selection.type !== "pronoun" && np.selection.possesor && !np.selection.possesor.shrunken); + const elements = [ + ...!inside ? [{np.selection.type !== "pronoun" ? np.selection.possesor : undefined}] : [], + {np.selection.adjectives}, +
{np.selection.ps[0][script]}
, + ]; + const el = script === "p" ? elements.reverse() : elements; return
-
- {!inside && {np.selection.type !== "pronoun" ? np.selection.possesor : undefined}} - {np.selection.adjectives} -
{np.selection.ps[0].f}
-
+ {el} +
NP {!inside ? <> @@ -227,13 +358,14 @@ export function NPBlock({ opts, children, inside, english }: { ({getEnglishPersonInfo(np.selection.person, "short")}) : <>}
- {english} + {!inside && {english}}
} -function Possesors({ opts, children }: { +function Possesors({ opts, children, script }: { opts: T.TextOptions, children: { shrunken: boolean, np: T.Rendered } | undefined, + script: "p" | "f", }) { if (!children) { return null; @@ -241,18 +373,18 @@ function Possesors({ opts, children }: { if (children.shrunken) { return null; } - const contraction = checkForContraction(children.np); - return
- {children.np.selection.type !== "pronoun" && {children.np.selection.possesor}} + {children.np.selection.type !== "pronoun" && {children.np.selection.possesor}}
{contraction &&
({contraction})
} -
-
du
+
+
{script === "p" ? "د" : "du"}
- {children.np} + {children.np}
@@ -260,32 +392,19 @@ function Possesors({ opts, children }: {
} -function checkForContraction(np: T.Rendered): string | undefined { - if (np.selection.type !== "pronoun") return undefined; - if (np.selection.person === T.Person.FirstSingMale || np.selection.person === T.Person.FirstSingFemale) { - return "zmaa" - } - if (np.selection.person === T.Person.SecondSingMale || np.selection.person === T.Person.SecondSingFemale) { - return "staa" - } - if (np.selection.person === T.Person.FirstPlurMale || np.selection.person === T.Person.FirstPlurFemale) { - return "zmoonG" - } - if (np.selection.person === T.Person.SecondPlurMale || np.selection.person === T.Person.SecondPlurFemale) { - return "staaso" - } - return undefined; -} - -function Adjectives({ opts, children }: { +function Adjectives({ opts, children, script }: { opts: T.TextOptions, children: T.Rendered[] | undefined, + script: "p" | "f", }) { if (!children) { return null; } + const c = script === "p" + ? children.reverse() + : children; return - {children.map(a => a.ps[0].f).join(" ")}{` `} + {c.map(a => a.ps[0][script]).join(" ")}{` `} } @@ -296,3 +415,21 @@ function EnglishBelow({ children: e }: { children: string | undefined }) { height: "1rem", }}>{e ? e : ""}
; } + +function checkForContraction(np: T.Rendered, script: "p" | "f"): string | undefined { + if (np.selection.type !== "pronoun") return undefined; + if (np.selection.person === T.Person.FirstSingMale || np.selection.person === T.Person.FirstSingFemale) { + return script === "f" ? "zmaa" : "زما"; + } + if (np.selection.person === T.Person.SecondSingMale || np.selection.person === T.Person.SecondSingFemale) { + return script === "f" ? "staa" : "ستا"; + } + if (np.selection.person === T.Person.FirstPlurMale || np.selection.person === T.Person.FirstPlurFemale) { + return script === "f" ? "zmoonG" : "زمونږ"; + } + if (np.selection.person === T.Person.SecondPlurMale || np.selection.person === T.Person.SecondPlurFemale) { + return script === "f" ? "staaso" : "ستاسو"; + } + return undefined; +} + diff --git a/src/components/blocks/KidDisplay.tsx b/src/components/blocks/KidDisplay.tsx index 7d31dce..714b977 100644 --- a/src/components/blocks/KidDisplay.tsx +++ b/src/components/blocks/KidDisplay.tsx @@ -1,15 +1,20 @@ import { baParticle } from "../../lib/grammar-units"; import * as T from "../../types"; +import Pashto from "../Pashto"; import Phonetics from "../Phonetics"; -function KidDisplay({ opts, kid }: { +function KidDisplay({ opts, kid, script }: { opts: T.TextOptions, kid: T.Kid, + script: "p" | "f", }) { + const ps = kid.type === "ba" + ? baParticle + : kid.ps; return
- {kid.type === "ba" - ? {baParticle} - : {kid.ps}} + {script === "p" + ? {ps} + : {ps}}
} diff --git a/src/components/ep-explorer/EPBlocksDisplay.tsx b/src/components/ep-explorer/EPBlocksDisplay.tsx deleted file mode 100644 index 88d08ba..0000000 --- a/src/components/ep-explorer/EPBlocksDisplay.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import * as T from "../../types"; -import Block from "../blocks/Block"; -import KidDisplay from "../blocks/KidDisplay"; - -function EPBlocksDisplay({ opts, rendered, justify }: { - opts: T.TextOptions, - rendered: T.EPRendered, - justify?: "left" | "right" | "center", -}) { - const blocks = rendered.omitSubject - ? rendered.blocks.filter(b => b.type !== "subjectSelection") - : rendered.blocks; - return
-
-
- -
- - {blocks.slice(1).map((block, i) => ( -
- -
- ))} -
-
-} - -function KidsSection({ opts, kids }: { - opts: T.TextOptions, - kids: T.Kid[], -}) { - return kids.length > 0 ?
-
- {kids.map(kid => ( - - ))} -
-
kids
-
: null; -} - -export default EPBlocksDisplay; \ No newline at end of file diff --git a/src/components/ep-explorer/EPDisplay.tsx b/src/components/ep-explorer/EPDisplay.tsx index b22970f..3b06c66 100644 --- a/src/components/ep-explorer/EPDisplay.tsx +++ b/src/components/ep-explorer/EPDisplay.tsx @@ -4,9 +4,10 @@ import { compileEP } from "../../lib/phrase-building/compile"; import ButtonSelect from "../ButtonSelect"; import { getPredicateSelectionFromBlocks, getSubjectSelection, getSubjectSelectionFromBlocks } from "../../lib/phrase-building/blocks-utils"; import { useState } from "react"; -import EPTextDisplay from "./EPTextDisplay"; -import EPBlocksDisplay from "./EPBlocksDisplay"; -type Mode = "text" | "blocks"; +import CompiledPTextDisplay from "../CompiledPTextDisplay"; +import EPBlocksDisplay from "../RenderedBlocksDisplay"; +import ModeSelect, { Mode, ScriptSelect } from "../DisplayModeSelect"; +import { useStickyState } from "../../library"; function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne }: { eps: T.EPSelectionState, @@ -16,6 +17,7 @@ function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne }: { onlyOne?: boolean, }) { const [mode, setMode] = useState("text"); + const [script, setScript] = useStickyState<"p" | "f">("f", "blockScriptChoice"); const EP = completeEPSelection(eps); const subject = getSubjectSelection(eps.blocks); @@ -36,7 +38,10 @@ function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne }: { const renderedPredicate = getPredicateSelectionFromBlocks(rendered.blocks).selection; return
- +
+ + {mode === "blocks" && } +
{setOmitSubject !== false ?
{mode === "text" - ? - : } + ? + : } {result.e &&
{(onlyOne ? [result.e[0]] : result.e).map((e, i) =>
{e}
)}
} @@ -63,14 +68,4 @@ function EPDisplay({ eps, opts, setOmitSubject, justify, onlyOne }: {
} -function ModeSelect({ value, onChange }: { value: Mode, onChange: (m: Mode) => void }) { - return
- {value === "text" ?
onChange("blocks")}> - -
:
onChange("text")}> - -
} -
; -} - export default EPDisplay; \ No newline at end of file diff --git a/src/components/ep-explorer/eps-reducer.ts b/src/components/ep-explorer/eps-reducer.ts index 1707b41..eeca13d 100644 --- a/src/components/ep-explorer/eps-reducer.ts +++ b/src/components/ep-explorer/eps-reducer.ts @@ -4,7 +4,7 @@ import { personNumber, } from "../../lib/misc-helpers"; import { isUnisexNounEntry } from "../../lib/type-predicates"; -import { checkEPForMiniPronounsError } from "../../lib/phrase-building/compile"; +import { checkForMiniPronounsError } from "../../lib/phrase-building/compile"; import { adjustSubjectSelection, getSubjectSelection, insertNewAP, removeAP, setAP, shiftBlock } from "../../lib/phrase-building/blocks-utils"; export type EpsReducerAction = { @@ -189,7 +189,7 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc } function ensureMiniPronounsOk(old: T.EPSelectionState, eps: T.EPSelectionState, sendAlert?: (msg: string) => void): T.EPSelectionState { - const error = checkEPForMiniPronounsError(eps); + const error = checkForMiniPronounsError(eps); if (error) { if (sendAlert) sendAlert(error); return old; diff --git a/src/components/vp-explorer/VPDisplay.tsx b/src/components/vp-explorer/VPDisplay.tsx index 141b96b..b37a694 100644 --- a/src/components/vp-explorer/VPDisplay.tsx +++ b/src/components/vp-explorer/VPDisplay.tsx @@ -1,56 +1,55 @@ import { compileVP } from "../../lib/phrase-building/compile"; import * as T from "../../types"; import AbbreviationFormSelector from "./AbbreviationFormSelector"; -import Examples from "../Examples"; import { getObjectSelection, getSubjectSelection } from "../../lib/phrase-building/blocks-utils"; +import { completeVPSelection } from "../../lib/phrase-building/vp-tools"; +import { renderVP } from "../../library"; +import ModeSelect, { Mode, ScriptSelect } from "../DisplayModeSelect"; +import { useState } from "react"; +import CompiledPTextDisplay from "../CompiledPTextDisplay"; +import RenderedBlocksDisplay from "../RenderedBlocksDisplay"; -function VPDisplay({ VP, opts, setForm }: { - VP: T.VPSelectionState | T.VPRendered, +function VPDisplay({ VPS, opts, setForm, justify, onlyOne }: { + VPS: T.VPSelectionState, opts: T.TextOptions, setForm: (form: T.FormVersion) => void, + justify?: "left" | "right" | "center", + onlyOne?: boolean, }) { - if (!("type" in VP)) { + const [mode, setMode] = useState("text"); + const [script, setScript] = useState<"p" | "f">("f"); + const VP = completeVPSelection(VPS); + if (!VP) { return
{(() => { - const subject = getSubjectSelection(VP.blocks).selection; - const object = getObjectSelection(VP.blocks).selection; - if (subject === undefined || object || undefined) { + const subject = getSubjectSelection(VPS.blocks).selection; + const object = getObjectSelection(VPS.blocks).selection; + if (subject === undefined || object === undefined) { return `Choose NP${((subject === undefined) && (object === undefined)) ? "s " : ""} to make a phrase`; } return `Choose/remove AP to complete the phrase`; })()}
; } - const result = compileVP(VP, { ...VP.form }); + const rendered = renderVP(VP); + const result = compileVP(rendered, rendered.form); return
- {"long" in result.ps ? -
- {/*
Long Verb:
*/} - - {/*
Short Verb:
*/} - - {result.ps.mini && <> - {/*
Mini Verb:
*/} - - } -
- : - } +
+ + {mode === "blocks" && } +
+ {mode === "text" + ? + : } {result.e &&
{result.e.map((e, i) =>
{e}
)}
}
} -function VariationLayer({ vs, opts }: { vs: T.PsString[], opts: T.TextOptions }) { - return
- {vs} -
; -} - export default VPDisplay; \ No newline at end of file diff --git a/src/components/vp-explorer/VPExplorer.tsx b/src/components/vp-explorer/VPExplorer.tsx index 3133679..2200698 100644 --- a/src/components/vp-explorer/VPExplorer.tsx +++ b/src/components/vp-explorer/VPExplorer.tsx @@ -15,7 +15,6 @@ import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationMod // @ts-ignore import LZString from "lz-string"; import { vpsReducer } from "./vps-reducer"; -import { getShrunkenServant } from "../../lib/phrase-building/compile"; import APPicker from "../ap-picker/APPicker"; import autoAnimate from "@formkit/auto-animate"; import { getObjectSelection, getSubjectSelection, isNoObject } from "../../lib/phrase-building/blocks-utils"; @@ -44,7 +43,7 @@ function VPExplorer(props: { props.loaded ? props.loaded : savedVps => makeVPSelectionState(props.verb, savedVps), - "vpsState14", + "vpsState15", flashMessage, ); const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">( @@ -143,7 +142,7 @@ function VPExplorer(props: { const VPS = completeVPSelection(vps); const phraseIsComplete = !!VPS; const rendered = VPS ? renderVP(VPS) : undefined; - const servantIsShrunk = !!(rendered ? getShrunkenServant(rendered) : undefined); + const servantIsShrunk = includesShrunkenServant(rendered?.kids); function toggleServantShrink() { adjustVps({ type: "toggle servant shrink", @@ -257,7 +256,12 @@ function VPExplorer(props: { : (vps.verb && block?.type === "objectSelection" && block.selection !== "none") ?
{(typeof block.selection === "number") - ?
Unspoken 3rd Pers. Masc. Plur.
+ ?
+ {roles.king === "object" + ?
setShowingExplanation({ role: "king", item: "object" })}>Object {roleIcon.king}
+ :
Object
} +
Unspoken 3rd Pers. Masc. Plur.
+
:
} {mode === "phrases" && } @@ -351,3 +355,10 @@ function getVPSFromUrl(): T.VPSelectionState | undefined { return JSON.parse(decoded) as T.VPSelectionState; } +function includesShrunkenServant(kids?: T.Kid[]): boolean { + if (!kids) return false; + return kids.some(k => ( + k.type === "mini-pronoun" && k.source === "servant" + )); +} + diff --git a/src/components/vp-explorer/VPExplorerQuiz.tsx b/src/components/vp-explorer/VPExplorerQuiz.tsx index 4e69a0c..ae9c681 100644 --- a/src/components/vp-explorer/VPExplorerQuiz.tsx +++ b/src/components/vp-explorer/VPExplorerQuiz.tsx @@ -15,10 +15,10 @@ import playAudio from "../../lib/play-audio"; import TensePicker from "./TensePicker"; import Keyframes from "../Keyframes"; import energyDrink from "./energy-drink.jpg"; -import { flattenLengths } from "../../lib/phrase-building/segment"; +import { flattenLengths } from "../../lib/phrase-building/compile"; import { concatPsString } from "../../lib/p-text-helpers"; import { isImperativeTense } from "../../lib/type-predicates"; -import { adjustObjectSelection, adjustSubjectSelection, getObjectSelection, getRenderedObjectSelection, getRenderedSubjectSelection, getSubjectSelection } from "../../lib/phrase-building/blocks-utils"; +import { adjustObjectSelection, adjustSubjectSelection, getObjectSelection, getObjectSelectionFromBlocks, getSubjectSelectionFromBlocks, getSubjectSelection, getVerbAndHeadFromBlocks } from "../../lib/phrase-building/blocks-utils"; const correctEmoji = ["✅", '🤓', "✅", '😊', "🌹", "✅", "✅", '🥳', "👏", "✅", "💯", "😎", "✅", "👍"]; @@ -113,8 +113,8 @@ function VPExplorerQuiz(props: { } } const rendered = renderVP(quizState.vps); - const subject: T.Rendered = getRenderedSubjectSelection(rendered.blocks).selection; - const object = getRenderedObjectSelection(rendered.blocks).selection; + const subject: T.Rendered = getSubjectSelectionFromBlocks(rendered.blocks).selection; + const object = getObjectSelectionFromBlocks(rendered.blocks).selection; const { e } = compileVP(rendered, { removeKing: false, shrinkServant: false }); return
@@ -346,18 +346,17 @@ function tickQuizState(startingWith: T.VPSelectionComplete | QuizState): QuizSta } function getBlanksAnswer(vps: T.VPSelectionComplete): { ps: T.PsString[], withBa: boolean } { - const { verb } = renderVP(vps); - const { head, rest } = verb.ps; - const ps = flattenLengths(rest).map(x => { + const { verb, perfectiveHead } = getVerbAndHeadFromBlocks(renderVP(vps).blocks); + const ps = flattenLengths(verb.block.ps).map(x => { const y = removeBa(x); - if (head) { - return concatPsString(head, y); + if (perfectiveHead) { + return concatPsString(perfectiveHead.ps, y); } return y; }); return { ps, - withBa: verb.hasBa, + withBa: verb.block.hasBa, } } diff --git a/src/lib/add-pronouns.ts b/src/lib/add-pronouns.ts deleted file mode 100644 index 0ec0dd5..0000000 --- a/src/lib/add-pronouns.ts +++ /dev/null @@ -1,518 +0,0 @@ -/** - * Copyright (c) 2021 lingdocs.com - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import { - ensureBaAt, - isAllOne, - isVerbBlock, - removeHead, - uniquePsStringArray, - splitOffLeapfrogWord, - removeObjComp, - psRemove, - psStringContains, -} from "../lib/p-text-helpers"; -import { - getPersonFromVerbForm, - pickPersInf, -} from "../lib/misc-helpers"; -import { - baParticle, - pronouns, -} from "../lib/grammar-units"; -import { - removeAccents, -} from "../lib/accent-helpers"; -import { concatPsString } from "../lib/p-text-helpers"; -import * as T from "../types"; -const pashtoCharacterRange = "\u0621-\u065f\u0670-\u06d3\u06d5" - -function getSplitHead(split: T.SplitInfo | undefined, matrixKey: T.PersonInflectionsField) { - if (!split) { - return undefined; - } - const fromMatrix = pickPersInf(split, matrixKey) - // doesn't matter what length it is, the head will always be the same - const pair = "long" in fromMatrix ? fromMatrix.long : fromMatrix; - return pair[0]; -} - -function formHasVariations(form: T.VerbForm | T.ImperativeForm | T.ParticipleForm | T.SentenceForm): boolean { - if ("mascSing" in form) { - return formHasVariations(form.mascSing); - } - if ("long" in form) { - return formHasVariations(form.long); - } - if (!isVerbBlock(form)) { - return false; - } - return !isAllOne(form); -} - -type Pronouns = undefined | { - subject: T.PsString | [T.PsString, T.PsString], - object?: T.PsString | [T.PsString, T.PsString], - mini: T.PsString, -} - -const nuParticle = { p: "نه", f: "nú" }; - -export default function addPronouns({ s, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel = "hard" }: { - s: T.SentenceForm, - subject: T.Person, - object: T.Person, - info: T.NonComboVerbInfo, - displayForm: T.DisplayFormForSentence, - intransitive: boolean, - ergative: boolean, - matrixKey: T.PersonInflectionsField, - negative: boolean, - englishConjugation?: T.EnglishVerbConjugation, - sentenceLevel?: "easy" | "medium" | "hard", -}): T.SentenceForm { - if ("long" in s) { - return { - long: addPronouns({ s: s.long, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel }) as T.ArrayOneOrMore, - short: addPronouns({ s: s.short, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel }) as T.ArrayOneOrMore, - ...s.mini ? { - mini: addPronouns({ s: s.mini, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel }) as T.ArrayOneOrMore, - } : {}, - } - } - function makeEnglish(englishBuilder: T.EnglishBuilder, englishConjugation: T.EnglishVerbConjugation): string[] { - const noObject = (intransitive || info.transitivity === "grammatically transitive" || info.type === "dynamic compound"); - const addRest = (s: string) => ( - `${s}${noObject ? "" : ` ${engObj(object)}`}${englishConjugation.ep ? ` ${englishConjugation.ep}` : ""}` - ); - return englishBuilder(subject, englishConjugation.ec, negative) - .map(addRest); - } - const firstOrSecondObjectPresent = [0,1,2,3,6,7,8,9].includes(object) && !displayForm.past; - const nearPronounPossible = (p: T.Person) => [4, 5, 10, 11].includes(p); - const noPronouns = - info.transitivity === "grammatically transitive" && displayForm.passive; - const noObjectPronoun = - intransitive || info.transitivity === "grammatically transitive" || - info.type === "dynamic compound" || info.type === "generative stative compound"; - const transDynCompPast = - info.transitivity === "transitive" && info.type === "dynamic compound" && displayForm.past; - const subjectPronoun = (getPersonFromVerbForm( - pronouns.far[ergative ? "inflected" : "plain"], - subject, - ) as T.ArrayOneOrMore)[0]; - const nearSubjectPronoun = (getPersonFromVerbForm( - pronouns.near[ergative ? "inflected" : "plain"], - subject, - ) as T.ArrayOneOrMore)[0]; - const objectPronoun = (getPersonFromVerbForm( - pronouns.far[firstOrSecondObjectPresent ? "inflected" : "plain"], - object, - ) as T.ArrayOneOrMore)[0]; - const nearObjectPronoun = (getPersonFromVerbForm( - pronouns.near[firstOrSecondObjectPresent ? "inflected" : "plain"], - object, - ) as T.ArrayOneOrMore)[0]; - const miniPronoun = (getPersonFromVerbForm( - pronouns.mini, - ergative ? subject : object, - ) as T.ArrayOneOrMore)[0]; - - const prns: Pronouns = noPronouns - ? undefined - : noObjectPronoun - ? { - subject: ((sentenceLevel === "hard") && nearPronounPossible(subject)) ? [subjectPronoun, nearSubjectPronoun] : subjectPronoun, - mini: miniPronoun, - } : { - subject: ((sentenceLevel === "hard") && nearPronounPossible(subject)) ? [subjectPronoun, nearSubjectPronoun] : subjectPronoun, - object: ((sentenceLevel === "hard") && nearPronounPossible(object)) ? [objectPronoun, nearObjectPronoun] : objectPronoun, - mini: miniPronoun, - }; - const english = (displayForm.englishBuilder && englishConjugation) - ? makeEnglish(displayForm.englishBuilder, englishConjugation) - : undefined; - - function attachPronounsToVariation(ps: T.PsString, prns: Pronouns): T.ArrayOneOrMore { - if (!prns) { - return [ps]; - } - if (Array.isArray(prns.subject)) { - return [ - ...attachPronounsToVariation(ps, { ...prns, subject: prns.subject[0] }), - ...attachPronounsToVariation(ps, { ...prns, subject: prns.subject[1] }), - ] as T.ArrayOneOrMore; - } - if (Array.isArray(prns.object)) { - return [ - ...attachPronounsToVariation(ps, { ...prns, object: prns.object[0] }), - ...attachPronounsToVariation(ps, { ...prns, object: prns.object[1] }), - ] as T.ArrayOneOrMore; - } - const splitHead = (displayForm.aspect && displayForm.aspect === "perfective") - ? getSplitHead(info[displayForm.past ? "root" : "stem"].perfectiveSplit, matrixKey) - : undefined; - const basicForms = (!prns.object) - // basic form with only one pronoun - ? makeBasicPronounForm(ps, splitHead, displayForm, info, negative, prns.subject) - : [ - // basic form two full pronouns - ...makeBasicPronounForm(ps, splitHead, displayForm, info, negative, prns.subject, prns.object), - // basic form one full, one mini pronoun - ...sentenceLevel !== "easy" ? makeBasicPronounForm( - ps, - splitHead, - displayForm, - info, - negative, - ergative ? prns.object : prns.subject, - prns.mini, - ) : [], - ] as T.ArrayOneOrMore; - - const ergativeGrammTrans = (info.transitivity === "grammatically transitive" && ergative); - const canWorkWithOnlyMini = (prns.object && !displayForm.secondPronounNeeded && formHasVariations(displayForm.form)) - || transDynCompPast || ergativeGrammTrans; - return [ - ...basicForms, - ...(sentenceLevel !== "easy" && canWorkWithOnlyMini) - ? makeOnlyMiniForm(ps, splitHead, displayForm, info, negative, prns.mini) - : [], - ].map((ps) => english ? { ...ps, e: english } : ps) as T.ArrayOneOrMore; - } - - // @ts-ignore - return s.reduce((variations, current) => ( - [...variations, ...uniquePsStringArray( - attachPronounsToVariation(current, prns) - )] - ), []) as T.ArrayOneOrMore; -} - -function nuMustGoAfterSplitHead(head: T.PsString) { - return ( - ["و", "وا"].includes(head.p) - || - head.p.slice(-1) === " " // compound splits - || - head.p.match(`[${pashtoCharacterRange}]* و`) - ); -} - -function spaceAfterSplitHead(head: T.PsString) { - if (nuMustGoAfterSplitHead(head) && head.p.slice(-1) !== " ") { - return { p: "", f: "-" } - } - return { p: " ", f: " " }; -} - -function makeBasicPronounForm( - ps: T.PsString, - splitHead: T.PsString | undefined, - displayForm: T.DisplayFormForSentence, - info: T.NonComboVerbInfo, - negative: boolean, - firstPronoun: T.PsString, - secondPronoun?: T.PsString, -): T.PsString[] { - if (!negative) { - return [ - ensureBaAt( - concatPsString( - firstPronoun, - " ", - secondPronoun ? concatPsString(secondPronoun, " ") : "", - ps, - ), - 1) - ]; - } - const objComplement = getObjComplement(info); - function negativeWOutSplit() { - if (!displayForm.reorderWithNegative) { - return [ - ensureBaAt( - concatPsString( - firstPronoun, - " ", - secondPronoun - ? concatPsString(secondPronoun, " ") - : objComplement - ? concatPsString(objComplement, " ") - : "", - nuParticle, - " ", - removeAccents(objComplement ? removeObjComp(objComplement, ps) : ps) - ), - 1), - ]; - } - const [beginning, end] = splitOffLeapfrogWord(ps); - return [ - ensureBaAt( - objComplement ? - concatPsString( - firstPronoun, - " ", - objComplement, - " ", - nuParticle, - " ", - end, - " ", - removeAccents(removeObjComp(objComplement, beginning)), - ) - : concatPsString( - firstPronoun, - " ", - secondPronoun ? concatPsString(secondPronoun, " ") : "", - nuParticle, - " ", - end, - " ", - removeAccents(beginning), - ), - 1), - ensureBaAt( - concatPsString( - firstPronoun, - " ", - secondPronoun ? concatPsString(secondPronoun, " ") : "", - removeAccents(beginning), - " ", - nuParticle, - " ", - end, - ), - 1), - ]; - } - function insertNegInSplit(splitHead: T.PsString) { - if (!displayForm.reorderWithNegative) { - return [ - ensureBaAt( - concatPsString( - firstPronoun, - " ", - secondPronoun ? concatPsString(secondPronoun, " ") : "", - removeAccents(splitHead), - spaceAfterSplitHead(splitHead), - nuParticle, - " ", - removeHead(splitHead, ps), - ), - 1), - ]; - } - const [beginning, end] = splitOffLeapfrogWord(ps); - return [ - ensureBaAt( - concatPsString( - firstPronoun, - " ", - secondPronoun ? concatPsString(secondPronoun, " ") : "", - removeAccents(splitHead), - spaceAfterSplitHead(splitHead), - nuParticle, - " ", - end, - " ", - removeHead(splitHead, beginning), - ), - 1), - ensureBaAt( - concatPsString( - firstPronoun, - " ", - secondPronoun ? concatPsString(secondPronoun, " ") : "", - removeAccents(splitHead), - spaceAfterSplitHead(splitHead), - nuParticle, - " ", - removeHead(splitHead, beginning), - " ", - end, - ), - 1), - ]; - } - if (splitHead) { - return nuMustGoAfterSplitHead(splitHead) ? insertNegInSplit(splitHead) : [ - ...insertNegInSplit(splitHead), - ...negativeWOutSplit(), - ] - } - return negativeWOutSplit(); -} - -function makeOnlyMiniForm( - ps: T.PsString, - splitHead: T.PsString | undefined, - displayForm: T.DisplayFormForSentence, - info: T.NonComboVerbInfo, - negative: boolean, - mini: T.PsString, -): T.PsString[] { - const objComplement = getObjComplement(info); - function reorderedNegativeAfterSplitHead(splitHead: T.PsString) { - const [beginning, end] = splitOffLeapfrogWord(ps); - return ensureBaAt( - objComplement ? - concatPsString( - objComplement, - " ", - mini, - " ", - removeAccents(removeObjComp(objComplement, splitHead)), - spaceAfterSplitHead(splitHead), - nuParticle, - " ", - end, - " ", - removeHead(splitHead, beginning), - ) - : concatPsString( - removeAccents(splitHead), - spaceAfterSplitHead(splitHead), - mini, - " ", - nuParticle, - " ", - end, - " ", - removeHead(splitHead, beginning), - ), - 1) - } - if (splitHead) { - // only mini pronoun with split - if (!displayForm.reorderWithNegative || !negative) { - const safeSplitHead = removeObjComp(objComplement, splitHead); - return [ensureBaAt( - concatPsString( - objComplement ? concatPsString(objComplement, " ", mini, " ") : "", - negative ? removeAccents(safeSplitHead) : safeSplitHead, - spaceAfterSplitHead(safeSplitHead), - !objComplement ? concatPsString(mini, " ") : "", - negative ? concatPsString(nuParticle, " ") : "", - removeHead(splitHead, ps) - ), - 1)]; - } - // if (!nuMustGoAfterSplitHead(splitHead)) { - // TODO: IS THIS A SEPERATELY NECESSARY THING FOR VERBS LIKE - // PREXODUL -- LIKE COULD YOU ALSO DO A VERSION WHERE THE SPLIT ISN'T USED - // return [reorderedNegativeAfterSplitHead(splitHead)]; - // } - return [reorderedNegativeAfterSplitHead(splitHead)]; - } - // only mini without split - const [beginning, end] = splitOffLeapfrogWord(ps); - if (!displayForm.reorderWithNegative || !negative) { - if (objComplement) { - return [ - concatPsString( - objComplement, - psStringContains(ps, concatPsString(baParticle, " ")) ? concatPsString(" ", baParticle, " ") : " ", - concatPsString(mini, " "), - negative ? concatPsString(" ", nuParticle, " ") : "", - removeObjComp(objComplement, psRemove(ps, concatPsString(baParticle, " "))), - ) - ]; - } - return [ - concatPsString( - psRemove(beginning, concatPsString(baParticle, " ")), - " ", - psStringContains(beginning, concatPsString(baParticle, " ")) ? concatPsString(baParticle, " ") : "", - negative ? concatPsString(" ", nuParticle, " ") : " ", - (beginning.p || negative) ? concatPsString(mini, " ") : "", - end, - (beginning.p || negative) ? "" : concatPsString(" ", mini), - ), - ]; - } - if (objComplement) { - return [ - ensureBaAt( - concatPsString( - objComplement, - " ", - mini, - " ", - nuParticle, - " ", - end, - " ", - removeObjComp(objComplement, beginning), - ), - 1), - ensureBaAt( - concatPsString( - objComplement, - " ", - mini, - " ", - removeObjComp(objComplement, beginning), - " ", - nuParticle, - " ", - end, - ), - 1), - ] - } - return [ - ensureBaAt( - concatPsString( - beginning, - " ", - mini, - " ", - nuParticle, - " ", - end, - ), - 1), - ensureBaAt( - concatPsString( - nuParticle, - " ", - end, - " ", - mini, - " ", - beginning, - ), - 1), - ]; -} - -function getObjComplement(info: T.NonComboVerbInfo): T.PsString | undefined { - return info.type === "dynamic compound" ? - (info.objComplement.plural ? info.objComplement.plural : info.objComplement.entry) : - undefined; -} - -function engObj(s: T.Person): string { - return (s === T.Person.FirstSingMale || s === T.Person.FirstSingFemale) - ? "me" - : (s === T.Person.FirstPlurMale || s === T.Person.FirstPlurFemale) - ? "us" - : (s === T.Person.SecondSingMale || s === T.Person.SecondSingFemale) - ? "you" - : (s === T.Person.SecondPlurMale || s === T.Person.SecondPlurFemale) - ? "you (pl.)" - : (s === T.Person.ThirdSingMale) - ? "him/it" - : (s === T.Person.ThirdSingFemale) - ? "her/it" - : (s === T.Person.ThirdPlurMale) - ? "them" - : "them (f.)"; -} diff --git a/src/lib/conjugation-forms.tsx b/src/lib/conjugation-forms.tsx deleted file mode 100644 index d815d94..0000000 --- a/src/lib/conjugation-forms.tsx +++ /dev/null @@ -1,740 +0,0 @@ -/** - * Copyright (c) 2021 lingdocs.com - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -// FOR ENGLISH FORMS -// GIVEN VERB INFO LIKE THIS -// ["hit", "hits", "hitting", "hit", "hit"] -// ["eat", "eats", "eating", "ate", "eaten"] -// ["see", "sees", "seeing", "saw", "seen"] - -// Present Perfect - -// Past Perfect -// - subj "had" (N && "not") v.4 obj - -// Future Perfect -// - subj "will have" (N && "not") v.4 obj - - - -import { - getPersonInflectionsKey, - pickPersInf, - getPersonFromVerbForm, - getVerbBlockPosFromPerson, -} from "./misc-helpers"; -import addPronouns from "./add-pronouns"; -import * as T from "../types"; -import { englishEquative } from "./grammar-units"; - -type FilterFunc = (form: any) => boolean; -type MapFunc = (opts: { - subject: T.Person, - object: T.Person, - displayForm: T.DisplayFormForSentence, - info: T.NonComboVerbInfo, - negative: boolean, - englishConjugation?: T.EnglishVerbConjugation, - sentenceLevel?: "easy" | "medium" | "hard", -}) => T.DisplayFormItem; - -/** - * Used to apply a filter function on both the levels of forms and subgroups - * - * @param input - * @param func - */ -const formFilter = ( - input: T.DisplayFormItem[], - func: FilterFunc | FilterFunc[] -): T.DisplayFormItem[] => { - // TODO: Better filtering that lets us filter things only in sub categories - - // recursive madness to apply an array of filters 🤪 - // i'm doing this because I couldn't get a compose function to work 🤷‍♂️ - if (Array.isArray(func)) { - if (func.length === 0) return input; - return formFilter( - formFilter(input, func[0]), - func.slice(1), - ); - } - return ( - input.filter(func) - .map((f) => ( - "content" in f - ? { ...f, content: f.content.filter(func) } - : f - )) - ); -}; - -/** - * Used to apply a filter function on both the levels of forms and subgroups - * - * @param input - * @param func - */ -const formMap = ( - input: T.DisplayFormItem[], - func: MapFunc, - info: T.NonComboVerbInfo, - subject: T.Person, - object: T.Person, - negative: boolean, - englishConjugation?: T.EnglishVerbConjugation, - sentenceLevel?: "easy" | "medium" | "hard", -): T.DisplayFormItem[] => { - return input.map((f) => ( - "content" in f - ? { ...f, content: formMap(f.content, func, info, subject, object, negative, englishConjugation, sentenceLevel) } - : func({ displayForm: f as T.DisplayFormForSentence, info, subject, object, negative, englishConjugation, sentenceLevel }) - )); -}; - -const makeSentence = ({ subject, object, info, displayForm, englishConjugation, negative, sentenceLevel }: { - subject: T.Person, - object: T.Person, - info: T.NonComboVerbInfo, - displayForm: T.DisplayFormForSentence, - negative: boolean, - sentenceLevel?: "easy" | "medium" | "hard", - englishConjugation?: T.EnglishVerbConjugation, -}): T.DisplayForm => { - const intransitive = info.transitivity === "intransitive" || !!displayForm.passive; - const ergative = !intransitive && !!displayForm.past; - function chooseConjugation(g: T.SingleOrLengthOpts): T.SentenceForm { - const person = ergative - ? object - : subject; - return getPersonFromVerbForm(g, person); - } - const f = displayForm.form; - // IMPORTANT TODO!!! -- IS THIS ALWAYS THE OBJECT HERE? - const matrixKey = getPersonInflectionsKey(object); - const matrixChosen = pickPersInf(f, matrixKey); - const conjugationChosen = chooseConjugation(matrixChosen); - const form = addPronouns({ - s: conjugationChosen, - subject, - object, - info, - displayForm, - intransitive, - ergative, - matrixKey, - negative, - englishConjugation, - sentenceLevel, - }); - return { - ...displayForm, - form, - }; -} - -function isToBe(v: T.EnglishVerbConjugationEc): boolean { - return (v[2] === "being"); -} - -const futureEngBuilder: T.EnglishBuilder = (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} will${n ? " not" : ""} ${isToBe(v) ? "be" : v[0]}`, -]); - -const formsOfConjugation = (conj: T.VerbConjugation): T.DisplayFormItem[] => [ - { - label: "Present", - aspect: "imperfective", - form: conj.imperfective.nonImperative, - formula: "Imperfective Stem + Present Ending", - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${isToBe(v) - ? `${engEquative("present", s)}${n ? " not" : ""}` - : `${n ? engPresC(s, ["don't", "doesn't"]) : ""} ${n ? v[0] : engPresC(s, v)}`}`, - `${engSubj(s)} ${engEquative("present", s)}${n ? " not" : ""} ${v[2]}`, - ]), - explanation: "Something that is happening, happens generally, or is definitely about to happen. ('I am ____ing', 'I _____')", - }, - { - label: "Subjunctive", - aspect: "perfective", - form: conj.perfective.nonImperative, - formula: "Perfective Stem + Present Ending", - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `that ${engSubj(s, true)}${n ? " won't" : " will"} ${isToBe(v) ? "be" : v[0]}`, - `should ${engSubj(s, true)}${n ? " not" : ""} ${isToBe(v) ? "be" : v[0]}`, - ]), - explanation: "Used for hypothetical statements about the desire, necessity, purpose, or possibility of something happening. Or for saying something should or shouldn't happen. ('Should I ____?', 'so that'll I'll _____')" - }, - { - label: "Imperfective Future", - aspect: "imperfective", - form: conj.imperfective.future, - advanced: true, - formula: "به - ba + Present", - sentence: true, - englishBuilder: futureEngBuilder, - explanation: "Saying something will happen, repeatedly or as an ongoing action", - }, - { - label: "Perfective Future", - aspect: "perfective", - form: conj.perfective.future, - advanced: true, - formula: "به - ba + Subjunctive", - sentence: true, - englishBuilder: futureEngBuilder, - explanation: "Saying something will happen as a one-time event - May also used when there is some doubt", - }, - ...conj.imperfective.imperative ? - [{ - label: "Imperfective Imperative", - aspect: "imperfective", - form: conj.imperfective.imperative, - formula: "Imperfective Stem + Imperative Ending", - explanation: "Commanding someone/people to do something repeatedly, or in general", - } as T.DisplayForm] : [], - ...conj.perfective.imperative ? - [{ - label: "Perfective Imperative", - aspect: "perfective", - form: conj.perfective.imperative, - formula: "Perfective Stem + Imperative Ending", - explanation: "Commanding someone/people to do something one time", - } as T.DisplayForm] : [], - { - label: "Continuous Past", - aspect: "imperfective", - form: conj.imperfective.past, - formula: "Imperfective Root + Past Ending", - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - // - subj pastEquative (N && "not") v.2 obj - `${engSubj(s)} ${engEquative("past", s)}${n ? " not" : ""} ${v[2]}`, - // - subj "would" (N && "not") v.0 obj - `${engSubj(s)} would${n ? " not" : ""} ${isToBe(v) ? "be" : v[0]}`, - // - subj pastEquative (N && "not") going to" v.0 obj - `${engSubj(s)} ${engEquative("past", s)}${n ? " not" : ""} going to ${isToBe(v) ? "be" : v[0]}`, - ]), - explanation: "Saying something was happening, or would happen ('I was ____ing', 'I would ____')", - past: true, - }, - { - label: "Simple Past", - aspect: "perfective", - form: conj.perfective.past, - formula: "Perfective Root + Past Ending", - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)}${isToBe(v) - ? ` ${engEquative("past", s)}${n ? " not" : ""}` - : `${n ? " did not" : ""} ${v[3]}`}`, - ]), - explanation: "Saying something happened ('I ____ed')", - past: true, - }, - { - label: "Perfect", - subgroup: "perfect", - sentence: true, - content: [ - { - label: "Half Perfect", - form: conj.perfect.halfPerfect, - past: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engHave(s)}${n ? " not" : ""} ${v[4]}`, - ]), - formula: "Past participle inflected", - secondPronounNeeded: true, - explanation: "The base of all perfect forms. Used on it's own as a sort of abreviated form of the present perfect.", - }, - { - label: "Past Perfect", - form: conj.perfect.past, - past: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} had${n ? " not" : ""} ${v[4]}`, - ]), - formula: "Past participle inflected + Past Equative", - explanation: "Talking about events that had happened in the past, or had affected a past situation ('I had ____ed')", - reorderWithNegative: true, - }, - { - label: "Present Perfect", - form: conj.perfect.present, - past: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engHave(s)}${n ? " not" : ""} ${v[4]}`, - ]), - formula: "Past participle inflected + Present Equative", - explanation: "Talking about that something happened in the past and it affects the present ('I have _____ed')", - reorderWithNegative: true, - }, - { - label: "Habitual Perfect", - form: conj.perfect.habitual, - past: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engHave(s)}${n ? " not" : ""} ${v[4]}`, - ]), - formula: "Past participle inflected + Habitual Equative", - explanation: "Talking about something that will have happened habitually", - reorderWithNegative: true, - }, - { - label: "Subjunctive Perfect", - form: conj.perfect.subjunctive, - past: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `that ${engSubj(s, true)} will have${n ? " not" : ""} ${v[4]}`, - ]), - formula: "Past participle inflected + Subjunctive Equative", - explanation: "expressing hope, desire, or judgement about an action having happened", - reorderWithNegative: true, - }, - { - label: "Future/Presumptive Perfect", - form: conj.perfect.future, - advanced: true, - past: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} will${n ? " not" : ""} have ${v[4]}`, - ]), - formula: "به - ba + Past participle Inflected + Future Equative", - explanation: "Talking about something that will have happened in the future, or guessing that the event will have occured presently ('I will have ____ed')", - reorderWithNegative: true, - }, - { - label: "Affirmational Perfect", - form: conj.perfect.affirmational, - advanced: true, - past: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} will${n ? " not" : ""} have ${v[4]}`, - ]), - explanation: "Affirming that an event will have taken place ('I will have ____ed')", - formula: "به - ba + Past Participle Inflected + Past Equative", - reorderWithNegative: true, - }, - { - label: "Conterfactual/Past Subjunctive Perfect", - form: conj.perfect.pastSubjunctiveHypothetical, - advanced: true, - past: true, - sentence: true, - secondPronounNeeded: true, - explanation: "Talking about an event that would have hypothetically taken place (but didn't), or that should have taken place but didn't", - formula: "به - ba + Past Participle Inflected + Past Subjunctive / Hypothetical Equative", - reorderWithNegative: true, - }, - ], - }, - { - label: "Modal (ability/possibility)", - subgroup: "modal", - sentence: true, - content: [ - { - label: "Present Modal", - aspect: "imperfective", - form: conj.imperfective.modal.nonImperative, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} can${n ? "'t" : ""} ${isToBe(v) ? "be" : v[0]}`, - ]), - formula: "Imperfective Root + Non-Inflectinig Ey-Tail + Subjunctive کېدل - to become", - explanation: "saying that something is possible currently or in general ('I can ____')", - reorderWithNegative: true, - }, - { - label: "Subjunctive Modal", - aspect: "perfective", - form: conj.perfective.modal.nonImperative, - advanced: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `that ${engSubj(s, true)} can${n ? "'t" : ""} ${isToBe(v) ? "be" : v[0]}`, - ]), - formula: "Perfective Root + Non-Inflectinig Ey-Tail + Subjunctive کېدل - to become", - explanation: "talking about the possibility of something in a subjunctive way ('so that I can ____')", - reorderWithNegative: true, - }, - { - label: "Imperfective Future Modal", - aspect: "imperfective", - form: conj.imperfective.modal.future, - advanced: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} will${n ? " not" : ""} be able to ${isToBe(v) ? "be" : v[0]}`, - ]), - formula: "به - ba + Present Modal", - explanation: "saying that something will be possible in general or in an ongoing sense in the future ('I'll be able to ____')", - reorderWithNegative: true, - }, - { - label: "Perfective Future Modal", - aspect: "perfective", - form: conj.perfective.modal.future, - advanced: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} will${n ? " not" : ""} be able to ${isToBe(v) ? "be" : v[0]}`, - ]), - formula: "به - ba + Subjunctive Modal", - explanation: "saying that something will be possible at a certain point in the future ('I'll be able to ____')", - reorderWithNegative: true, - }, - { - label: "Continous Past Modal", - aspect: "imperfective", - form: conj.imperfective.modal.past, - advanced: true, - past: true, - sentence: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engEquative("past", s)} ${n ? " not" : ""} able to ${isToBe(v) ? "be" : v[0]}`, - `${engSubj(s)} could${n ? " not" : ""} ${v[0]}`, - ]), - formula: "Imperfective Root + Non-Inflectinig Ey-Tail + Simple Past کېدل - to become", - explanation: "saying that something was possible in general, in an ongoing sense ('I was able to ____', ie. 'I could do ____ any time')", - reorderWithNegative: true, - }, - { - label: "Simple Past Modal", - aspect: "perfective", - form: conj.perfective.modal.past, - formula: "Perfective Root + Non-Inflectinig Ey-Tail + Simple Past کېدل - to become", - explanation: "saying that something was possible at a certain point in time ('I was able to ____, at one particular point in time')", - past: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engEquative("past", s)} ${n ? " not" : ""} able to ${isToBe(v) ? "be" : v[0]}`, - `${engSubj(s)} could${n ? " not" : ""} ${isToBe(v) ? "be" : v[0]}`, - ]), - sentence: true, - advanced: true, - reorderWithNegative: true, - }, - { - label: "Imperfective hypothetical/wildcard Past Modal", - aspect: "imperfective", - form: conj.imperfective.modal.hypotheticalPast, - formula: "Imperfective Root + Non-Inflectinig Ey-Tail + ش - sh + Non-Inflectinig Ey-Tail", - explanation: "saying that something was possible in general, in an ongoing sense ('I was able to ____', ie. 'I could do ____ any time'). This 'wildcard' form can be used either to talk about hypothetical things, or to avoid worrying about verb agreement", - past: true, - sentence: true, - advanced: true, - reorderWithNegative: true, - }, - { - label: "Perfective hypothetical/wildcard Past Modal", - aspect: "perfective", - form: conj.perfective.modal.hypotheticalPast, - formula: "Perfective Root + Non-Inflectinig Ey-Tail + ش - sh + Non-Inflectinig Ey-Tail", - explanation: "saying that something was possible at a certain point in time ('I was able to ____, at one particular point in time'). This 'wildcard' form can be used either to talk about hypothetical things, or to avoid worrying about verb agreement", - past: true, - sentence: true, - advanced: true, - reorderWithNegative: true, - }, - ], - }, - { - label: "Hypothetical/Wish", - advanced: true, - form: conj.hypothetical, - formula: "Imperfective Root + Non-Inflecting Ey-Tail", - explanation: "Talking about a hypothetical, unreal situation, or something that is wished for ('If I ____')", - past: true, - }, - { - label: "Participle", - subgroup: "participle", - advanced: true, - content: [ - { - label: "Present Participle", - form: conj.participle.present, - formula: "Short form of Ininitive Root + ونکی - oonkey", - explanation: "Making a verb into a noun or adjective, talking about a person or thing that does or experiences something. Also used to say something is about to happen. ('____ing', '____er')", - }, - { - label: "Past Participle", - form: conj.participle.past, - past: true, - formula: "Infinitive Root or Special Form + Inflecting Ey-Tail", - explanation: "Making a verb into a noun or adjective, talking about how a person or thing did or experienced something. ('____ed')", - }, - ], - }, - ...conj.passive ? - [{ - label: "Passive", - subgroup: "passive", - advanced: true, - sentence: true, - content: [ - { - label: "Passive Present", - aspect: "imperfective", - form: conj.passive.imperfective.nonImperative, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engEquative("present", s)}${n ? " not" : ""} being ${v[4]}`, - ]), - formula: "Long Imperfective Root + Present کېدل - to become", - explanation: "Saying that something is being done or is done in general, without mentioning the subject/agent. ('I am being ____en')", - }, - { - label: "Passive Subjunctive", - aspect: "perfective", - form: conj.passive.perfective.nonImperative, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `that ${engSubj(s, true)} will${n ? " not" : ""} be ${v[4]}`, - ]), - formula: "Long Perfective Root + Subjunctive کېدل - to become", - explanation: "Saying that something should be done, or giving a purpose for something being done etc., without mentioning the subject/agent. ('Should I be ____en?', 'So that I'll be ____en')" - }, - { - label: "Passive Imperfective Future", - aspect: "imperfective", - form: conj.passive.imperfective.future, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} will${n ? " not" : ""} be ${v[4]}`, - ]), - formula: "به - ba + Passive Present", - explanation: "Saying something will be done as a one-time event, without mentioning the subject/agent.", - }, - { - label: "Passive Perfective Future", - aspect: "perfective", - form: conj.passive.perfective.future, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} will${n ? " not" : ""} be ${v[4]}`, - ]), - formula: "به - ba + Passive Subjunctive", - explanation: "Saying something will be done in an ongoing or repeated sense, without mentioning the subject/agent." - }, - { - label: "Passive Continuous Past", - aspect: "imperfective", - form: conj.passive.imperfective.past, - past: true, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engEquative("past", s)}${n ? " not" : ""} being ${v[4]}`, - ]), - formula: "Long Imperfective Root + Continuous Past کېدل - to become", - explanation: "Saying that something was being done, or would be done, without mentioning the subject/agent. ('I was being ____en', 'I would be ____en')", - }, - { - label: "Passive Simple Past", - aspect: "perfective", - form: conj.passive.perfective.past, - past: true, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engEquative("past", s)}${n ? " not" : ""} ${v[4]}`, - ]), - formula: "Long Perfective Root + Simple Past کېدل - to become", - explanation: "Saying that was done as a one-time event, without mentioning the subject/agent. ('I was ____en')" - }, - { - label: "Passive Perfect", - subgroup: "passive perfect", - passive: true, - sentence: true, - content: [ - { - label: "Passive Half Perfect", - form: conj.passive.perfect.halfPerfect, - past: true, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engHave(s)}${n ? " not" : ""} been ${v[4]}`, - ]), - formula: "Infinitive + کېدل past participle inflected", - explanation: "The base of all perfect forms. Used on it's own as a sort of abbreviated form of the present perfect. (Passive voice)", - }, - { - label: "Passive Past Perfect", - form: conj.passive.perfect.past, - past: true, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} had${n ? " not" : ""} been ${v[4]}`, - ]), - formula: "Infinitive + کېدل past participle inflected + Past Equative", - explanation: "Talking about events that had happened in the past, or had affected a past situation (Passive voice) ('I had been ____ed')", - }, - { - label: "Passive Present Perfect", - form: conj.passive.perfect.present, - past: true, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engHave(s)}${n ? " not" : ""} been ${v[4]}`, - ]), - formula: "Infinitive + کېدل past participle inflected + Present Equative", - explanation: "Talking about that something happened in the past and it affects the present (Passive voice) ('I have been _____ed')", - }, - { - label: "Passive Habitual Perfect", - form: conj.passive.perfect.subjunctive, - past: true, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} ${engHave(s)}${n ? " not" : ""} been ${v[4]}`, - ]), - formula: "Infinitive + کېدل past participle inflected + Habitual Equative", - }, - { - label: "Passive Subjunctive Perfect", - form: conj.passive.perfect.subjunctive, - past: true, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `that ${engSubj(s, true)} will${n ? " not" : ""} have been ${v[4]}`, - ]), - formula: "Infinitive + کېدل past participle inflected + Subjunctive Equative", - }, - { - label: "Passive Future/Presumptive Perfect", - form: conj.passive.perfect.future, - advanced: true, - past: true, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} will${n ? " not" : ""} have been ${v[4]}`, - ]), - formula: "به - ba + Infinitive + کېدل past participle inflected + Future Equative", - explanation: "Talking about something that will have happened in the future, or guessing that the event will have occured presently (Passive voice) ('I will have been ____ed')", - }, - { - label: "Passive Affirmational Perfect", - form: conj.passive.perfect.affirmational, - advanced: true, - past: true, - sentence: true, - passive: true, - englishBuilder: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ - `${engSubj(s)} will${n ? " not" : ""} have been ${v[4]}`, - ]), - explanation: "Affirming that an event will have taken place (Passive voice) ('I will have been ____ed')", - formula: "به - ba + Infinitive + کېدل past participle inflected + Past Equative" - }, - { - label: "Passive Past Subjunctive / Hypothetical Perfect", - form: conj.passive.perfect.pastSubjunctiveHypothetical, - advanced: true, - past: true, - sentence: true, - passive: true, - explanation: "Talking about an event that would have hypothetically taken place, or that should have taken place (Passive voice) ('I would have been ____ed')", - formula: "به - ba + Infinitive + کېدل past participle inflected + Past Subjunctive / Hypothetical Equative" - }, - ], - }, - ] - } as T.DisplayFormSubgroup] - : [], -]; - -export const getForms = ({ conj, filterFunc, mode, subject, object, sentenceLevel, englishConjugation, negative }: { - conj: T.VerbConjugation, - englishConjugation?: T.EnglishVerbConjugation - filterFunc?: FilterFunc | FilterFunc[], - mode: "chart" | "sentence", - subject: T.Person, - object: T.Person, - sentenceLevel?: "easy" | "medium" | "hard", - negative: boolean, -}): T.DisplayFormItem[] => { - const forms = formsOfConjugation(conj); - const formsToUse = filterFunc - ? formFilter(forms, filterFunc) - : forms; - return mode === "chart" - ? formsToUse - : formMap( - formFilter(formsToUse, (f) => f.sentence), - makeSentence, - conj.info, - subject, - object, - negative, - englishConjugation, - sentenceLevel, - ); -} - -function isThirdPersonSing(p: T.Person): boolean { - return ( - p === T.Person.ThirdSingMale || - p === T.Person.ThirdSingFemale - ); -} - -function engPresC(s: T.Person, ec: T.EnglishVerbConjugationEc | [string, string]): string { - return isThirdPersonSing(s) ? ec[1] : ec[0]; -} - -function engEquative(tense: "past" | "present", s: T.Person): string { - const [row, col] = getVerbBlockPosFromPerson(s); - return englishEquative[tense][row][col]; -} - -function engHave(s: T.Person): string { - return isThirdPersonSing(s) ? "has" : "have"; -} - -function engSubj(s: T.Person, lowerCase?: boolean): string { - const pronoun = (s === T.Person.FirstSingMale || s === T.Person.FirstSingFemale) - ? "I" - : (s === T.Person.FirstPlurMale || s === T.Person.FirstPlurFemale) - ? "We" - : (s === T.Person.SecondSingMale || s === T.Person.SecondSingFemale) - ? "You" - : (s === T.Person.SecondPlurMale || s === T.Person.SecondPlurFemale) - ? "You (pl.)" - : (s === T.Person.ThirdSingMale) - ? "He/it" - : (s === T.Person.ThirdSingFemale) - ? "She/it" - : (s === T.Person.ThirdPlurMale) - ? "They" - : "They (f.)"; - return (lowerCase && pronoun !== "I") - ? pronoun.toLowerCase() - : pronoun; -} diff --git a/src/lib/grammar-units.ts b/src/lib/grammar-units.ts index 8d5849e..3921636 100644 --- a/src/lib/grammar-units.ts +++ b/src/lib/grammar-units.ts @@ -9,6 +9,14 @@ import { kawulStat } from "./irregular-conjugations"; import * as T from "../types"; +export const negativeParticle: { + imperative: T.PsString, + nonImperative: T.PsString, +} = { + nonImperative: { p: "نه", f: "nú" }, + imperative: { p: "مه", f: "mú" }, +}; + export const presentEndings: T.VerbBlock = [ [ [{ diff --git a/src/lib/p-text-helpers.ts b/src/lib/p-text-helpers.ts index b392cac..b4678da 100644 --- a/src/lib/p-text-helpers.ts +++ b/src/lib/p-text-helpers.ts @@ -687,6 +687,21 @@ export function uniquePsStringArray(arr: T.PsString[]): T.PsString[] { ].map((string) => JSON.parse(string)) as T.PsString[]; } +export function splitOffLeapfrogWordFull(ps: T.SingleOrLengthOpts): [T.SingleOrLengthOpts, T.SingleOrLengthOpts] { + if ("long" in ps) { + const [shortA, shortB] = splitOffLeapfrogWordFull(ps.short) as [T.PsString[], T.PsString[]]; + const [longA, longB] = splitOffLeapfrogWordFull(ps.long) as [T.PsString[], T.PsString[]]; + return [{ long: longA, short: shortA }, { long: longB, short: shortB }]; + } + return ps.reduce((accum, curr): [T.PsString[], T.PsString[]] => { + const [front, back] = splitOffLeapfrogWord(curr); + return [ + [...accum[0], front], + [...accum[1], back], + ]; + }, [[], []] as [T.PsString[], T.PsString[]]) +} + export function splitOffLeapfrogWord(ps: T.PsString): [T.PsString, T.PsString] { const pWords = ps.p.split(" "); const fWords = ps.f.split(" "); @@ -984,8 +999,12 @@ export function psStringFromEntry(entry: T.PsString): T.PsString { }; } -export function getLength(x: T.SingleOrLengthOpts, length: "long" | "short"): U { - return ("long" in x) ? x[length] : x; +export function getLength(x: T.SingleOrLengthOpts, length: "long" | "short" | "mini"): U { + if ("long" in x) { + const s = x[length]; + return s ? s : x.short; + } + return x; } export function getLong(x: T.SingleOrLengthOpts): U { diff --git a/src/lib/phrase-building/blocks-utils.ts b/src/lib/phrase-building/blocks-utils.ts index 44ec07d..7e183a9 100644 --- a/src/lib/phrase-building/blocks-utils.ts +++ b/src/lib/phrase-building/blocks-utils.ts @@ -11,24 +11,61 @@ export function getSubjectSelection(blocks: T.EPSBlock[] | T.EPSBlockComplete[] return b.block; } -export function getSubjectSelectionFromBlocks(blocks: T.Block[]): T.Rendered { - const b = blocks.find(f => f.type === "subjectSelection"); +export function getSubjectSelectionFromBlocks(blocks: T.Block[][]): T.Rendered { + const b = blocks[0].find(f => f.type === "subjectSelection"); if (!b || b.type !== "subjectSelection") { throw new Error("subjectSelection not found in blocks"); } return b; } -export function getPredicateSelectionFromBlocks(blocks: T.Block[]): T.Rendered { - const b = blocks.find(f => f.type === "predicateSelection"); +export function getObjectSelectionFromBlocks(blocks: T.Block[][]): T.Rendered { + const b = blocks[0].find(f => f.type === "objectSelection"); + if (!b || b.type !== "objectSelection") { + throw new Error("objectSelection not found in blocks"); + } + return b; +} + +export function getVerbFromBlocks(blocks: T.Block[][]): T.VerbRenderedBlock { + const b = blocks[0].find(f => f.type === "verb"); + const p = blocks[0].find(f => f.type === "perfectParticipleBlock"); + const m = blocks[0].find(f => f.type === "modalVerbBlock"); + const v = (b && b.type === "verb") + ? b + : (p && p.type === "perfectParticipleBlock") + ? p.verb + : (m && m.type === "modalVerbBlock") + ? m.verb + : undefined; + if (!v) { + throw new Error("verbSelection not found in blocks"); + } + return v; +} + +export function getVerbAndHeadFromBlocks(blocks: T.Block[][]): { verb: T.VerbRenderedBlock, perfectiveHead: T.PerfectiveHeadBlock } { + const verb = getVerbFromBlocks(blocks); + const perfectiveHead = blocks[0].find(f => f.type === "perfectiveHead"); + if (!perfectiveHead || perfectiveHead.type !== "perfectiveHead") { + throw new Error("perfectiveHead not found in blocks"); + } + return { + verb, + perfectiveHead, + }; +} + +export function getPredicateSelectionFromBlocks(blocks: T.Block[][]): T.Rendered { + const b = blocks[0].find(f => f.type === "predicateSelection"); if (!b || b.type !== "predicateSelection") { throw new Error("predicateSelection not found in blocks"); } return b; } -export function getAPsFromBlocks(blocks: T.Block[]): T.Rendered[] { - return blocks.filter(b => b.type === "AP") as T.Rendered[]; +export function getAPsFromBlocks(blocks: T.Block[][]): T.Rendered[] { + return blocks[0].filter(b => b.type === "AP") as T.Rendered[]; } export function getObjectSelection(blocks: T.VPSBlockComplete[]): T.ObjectSelectionComplete; @@ -41,23 +78,6 @@ export function getObjectSelection(blocks: T.VPSBlock[] | T.VPSBlockComplete[]): return b.block; } -export function getRenderedSubjectSelection(blocks: (T.Rendered | T.Rendered | T.Rendered | T.RenderedVPSBlock)[]): T.Rendered { - const b = blocks.find(f => typeof f === "object" && f.type === "subjectSelection"); - if (!b || typeof b !== "object" || b.type !== "subjectSelection") { - throw new Error("subjectSelection not found in blocks"); - } - return b; -} - -export function getRenderedObjectSelection(blocks: T.RenderedVPSBlock[]): T.Rendered { - const b = blocks.find(f => typeof f === "object" && f.type === "objectSelection"); - if (!b || typeof b !== "object" || b.type !== "objectSelection") { - throw new Error("objectSelection not found in blocks"); - } - return b; -} - - export function makeEPSBlocks(): T.EPSBlock[] { return [ { @@ -206,29 +226,77 @@ export function isNoObject(b: T.VPSBlock["block"] | T.EPSBlock["block"]): b is { return !!(b && b.type === "objectSelection" && b.selection === "none"); } -export function specifyEquativeLength(blocks: T.Block[], length: "long" | "short"): T.Block[] { - const i = blocks.findIndex(b => b.type === "equative"); - if (i === -1) throw new Error("equative block not found in EPRendered"); - const eq = blocks[i]; - if (eq.type !== "equative") throw new Error("error searching for equative block"); - const adjusted = [...blocks]; - adjusted[i] = { - ...eq, - equative: { - ...eq.equative, - ps: getLength(eq.equative.ps, length), - }, - }; - return adjusted; +export function specifyEquativeLength(blocksWVars: T.Block[][], length: "long" | "short"): T.Block[][] { + function specify(blocks: T.Block[]): T.Block[] { + const i = blocks.findIndex(b => b.type === "equative"); + if (i === -1) throw new Error("equative block not found in EPRendered"); + const eq = blocks[i]; + if (eq.type !== "equative") throw new Error("error searching for equative block"); + const adjusted = [...blocks]; + adjusted[i] = { + ...eq, + equative: { + ...eq.equative, + ps: getLength(eq.equative.ps, length), + }, + }; + return adjusted; + } + return blocksWVars.map(specify); } -export function hasEquativeWithLengths(blocks: T.Block[]): boolean { - const equative = blocks.find(x => x.type === "equative"); +export function specifyVerbLength(blocksWVars: T.Block[][], length: "long" | "short" | "mini"): T.Block[][] { + function specify(blocks: T.Block[]): T.Block[] { + return blocks.map((block) => { + if (block.type === "verb") { + const v: T.VerbRenderedBlock = { + ...block, + block: { + ...block.block, + ps: getLength(block.block.ps, length), + }, + }; + return v; + } + if (block.type === "perfectParticipleBlock") { + const p: T.PerfectParticipleBlock = { + ...block, + ps: getLength(block.ps, length), + }; + return p; + } + if (block.type === "modalVerbBlock") { + const m: T.ModalVerbBlock = { + ...block, + ps: getLength(block.ps, length), + }; + return m; + } + return block; + }); + } + return blocksWVars.map(specify); +} + +export function hasEquativeWithLengths(blocks: T.Block[][]): boolean { + const equative = blocks[0].find(x => x.type === "equative"); if (!equative) throw new Error("equative not found in blocks"); if (equative.type !== "equative") throw new Error("error finding equative in blocks"); return "long" in equative.equative.ps; } +export function hasVerbWithLengths(blocks: T.Block[][]): boolean { + // TODO: handle length options with perfect verb equative as well? + const verb = blocks[0].find(x => (x.type === "verb" || x.type === "perfectParticipleBlock" || x.type === "modalVerbBlock")); + if (!verb) throw new Error("verb not found in blocks"); + if (verb.type !== "verb" && verb.type !== "perfectParticipleBlock" && verb.type !== "modalVerbBlock") throw new Error("error finding verb in blocks"); + return ( + (verb.type === "verb" && "long" in verb.block.ps) + || (verb.type === "perfectParticipleBlock" && "long" in verb.ps) + || (verb.type === "modalVerbBlock" && "long" in verb.ps) + ); +} + function arrayMove(ar: X[], old_index: number, new_index: number): X[] { const arr = [...ar]; const new_i = (new_index >= arr.length) diff --git a/src/lib/phrase-building/compile.ts b/src/lib/phrase-building/compile.ts index 7af63e6..381bb08 100644 --- a/src/lib/phrase-building/compile.ts +++ b/src/lib/phrase-building/compile.ts @@ -1,33 +1,27 @@ import * as T from "../../types"; import { - concatPsString, getLong, + concatPsString, getLong, getShort, } from "../p-text-helpers"; -import { - Segment, - makeSegment, - flattenLengths, - combineSegments, - splitOffLeapfrogWord, - putKidsInKidsSection as oldPutKidsInKidsSection, -} from "./segment"; -import { - removeAccents, -} from "../accent-helpers"; +import { negativeParticle } from "../../lib/grammar-units"; import * as grammarUnits from "../grammar-units"; import { - removeBa, removeDuplicates, } from "./vp-tools"; -import { isImperativeTense, isModalTense, isPerfectTense } from "../type-predicates"; import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools"; -import { getVerbBlockPosFromPerson } from "../misc-helpers"; -import { pronouns } from "../grammar-units"; import { completeEPSelection, renderEP } from "./render-ep"; import { completeVPSelection } from "./vp-tools"; import { renderVP } from "./render-vp"; -import { getAPsFromBlocks, getPredicateSelectionFromBlocks, getRenderedObjectSelection, getRenderedSubjectSelection, getSubjectSelectionFromBlocks, hasEquativeWithLengths, specifyEquativeLength } from "./blocks-utils"; - -// TODO: GET BLANKING WORKING! +import { + getAPsFromBlocks, + getObjectSelectionFromBlocks, + getPredicateSelectionFromBlocks, + getSubjectSelectionFromBlocks, + getVerbFromBlocks, + hasEquativeWithLengths, + hasVerbWithLengths, + specifyEquativeLength, + specifyVerbLength, +} from "./blocks-utils"; const blank: T.PsString = { p: "______", @@ -37,228 +31,31 @@ type BlankoutOptions = { equative?: boolean, ba?: boolean, kidsSection?: boolean const kidsBlank: T.PsString = { p: "___", f: "___" }; -export function compileVP(VP: T.VPRendered, form: T.FormVersion): { ps: T.SingleOrLengthOpts, e?: string [] }; -export function compileVP(VP: T.VPRendered, form: T.FormVersion, combineLengths: true): { ps: T.PsString[], e?: string [] }; -export function compileVP(VP: T.VPRendered, form: T.FormVersion, combineLengths?: true): { ps: T.SingleOrLengthOpts, e?: string [] } { - const verb = VP.verb.ps; - const { kids, blocks } = getVPSegmentsAndKids(VP, form); - const psResult = compilePs({ - blocks, - 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 : compileEnglishVP(VP), - }; -} -type CompilePsInput = { - blocks: Segment[], - kids: Segment[], - verb: { - head: T.PsString | undefined, - rest: T.SingleOrLengthOpts, - }, - VP: T.VPRendered, -} +// function compilePs({ blocks, kids, verb: { head, rest }, VP }: CompilePsInput): T.SingleOrLengthOpts { +// if ("long" in rest) { +// return { +// long: compilePs({ blocks, verb: { head, rest: rest.long }, VP, kids }) as T.PsString[], +// short: compilePs({ blocks, verb: { head, rest: rest.short }, VP, kids }) as T.PsString[], +// ...rest.mini ? { +// mini: compilePs({ blocks, verb: { head, rest: rest.mini }, VP, kids }) as T.PsString[], +// } : {}, +// }; +// } +// const verbWNegativeVersions = arrangeVerbWNegative(head, rest, VP.verb); -function compilePs({ blocks, kids, verb: { head, rest }, VP }: CompilePsInput): T.SingleOrLengthOpts { - if ("long" in rest) { - return { - long: compilePs({ blocks, verb: { head, rest: rest.long }, VP, kids }) as T.PsString[], - short: compilePs({ blocks, verb: { head, rest: rest.short }, VP, kids }) as T.PsString[], - ...rest.mini ? { - mini: compilePs({ blocks, 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) => { - // for each permutation of the possible ordering of NPs and Verb + nu - // 1. put in kids in the kids section - const segments = oldPutKidsInKidsSection([...blocks, ...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); - })); -} - -export function getShrunkenServant(VP: T.VPRendered): Segment | undefined { - const shrinkServant = VP.form.shrinkServant && !(VP.isCompound === "dynamic" && !VP.isPast); - const toShrinkServant: T.Rendered | undefined = (() => { - if (!shrinkServant) return undefined; - if (!VP.servant) return undefined; - const servant = VP.servant === "subject" - ? getRenderedSubjectSelection(VP.blocks).selection - : getRenderedObjectSelection(VP.blocks).selection; - if (typeof servant !== "object") return undefined; - return servant; - })(); - return toShrinkServant ? shrinkNP(toShrinkServant) : undefined; -} - -export function getVPSegmentsAndKids(VP: T.VPRendered, form?: T.FormVersion): { kids: Segment[], blocks: Segment[] } { - const removeKing = VP.form.removeKing && !(VP.isCompound === "dynamic" && VP.isPast); - const shrunkenServant = getShrunkenServant(VP); - const possToShrink = findPossesivesToShrinkInVP(VP, { - shrunkServant: !!shrunkenServant, - removedKing: removeKing, - }); - const subject = getRenderedSubjectSelection(VP.blocks).selection; - const blocks = VP.blocks.reduce((accum, block) => { - if (block.type === "subjectSelection") { - if (VP.servant === "subject" && shrunkenServant) return accum; - if (VP.king === "subject" && removeKing) return accum; - return [ - ...accum, - makeSegment(getPashtoFromRendered(block.selection, false)), - ]; - } - if (block.type === "objectSelection") { - if (VP.servant === "object" && shrunkenServant) return accum; - if (VP.king === "object" && removeKing) return accum; - if (typeof block.selection !== "object") return accum; - return [ - ...accum, - makeSegment(getPashtoFromRendered(block.selection, subject.selection.person)), - ] - } - return [ - ...accum, - makeSegment(getPashtoFromRendered(block, false)), - ]; - }, [] as Segment[]); - return { - kids: orderKidsSection([ - ...VP.verb.hasBa - ? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] : [], - ...shrunkenServant - ? [shrunkenServant] : [], - ...possToShrink.map(shrinkNP), - ]), - blocks: blocks, - }; -} - -function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[], V: T.VerbRendered): Segment[][] { - const hasLeapfrog = isPerfectTense(V.tense) || isModalTense(V.tense); - 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 = isImperativeTense(V.tense) - ? { p: "مه", f: "mú" } - : { p: "نه", f: "nú" }; - if (!headSegment) { - if ("front" in rest) { - return [ - // pefect nu dey me leeduley and nu me dey leeduley - // actually don't think this is correct - keeping it out for now - // [ - // 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 }), - ]] : [], - ]; -} +// // put together all the different possible permutations based on: +// // a. potential different versions of where the nu goes +// return removeDuplicates(verbWNegativeVersions.flatMap((verbSegments) => { +// // for each permutation of the possible ordering of NPs and Verb + nu +// // 1. put in kids in the kids section +// const segments = oldPutKidsInKidsSection([...blocks, ...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); +// })); +// } export function compileEP(EP: T.EPRendered): { ps: T.SingleOrLengthOpts, e?: string[] }; export function compileEP(EP: T.EPRendered, combineLengths: true, blankOut?: BlankoutOptions): { ps: T.PsString[], e?: string[] }; @@ -270,7 +67,36 @@ export function compileEP(EP: T.EPRendered, combineLengths?: boolean, blankOut?: }; } -function compileEPPs(blocks: T.Block[], kids: T.Kid[], omitSubject: boolean, blankOut?: BlankoutOptions): T.SingleOrLengthOpts { +export function compileVP(VP: T.VPRendered, form: T.FormVersion): { ps: T.SingleOrLengthOpts, e?: string [] }; +export function compileVP(VP: T.VPRendered, form: T.FormVersion, combineLengths: true): { ps: T.PsString[], e?: string [] }; +export function compileVP(VP: T.VPRendered, form: T.FormVersion, combineLengths?: true): { ps: T.SingleOrLengthOpts, e?: string [] } { + const verb = getVerbFromBlocks(VP.blocks).block; + const psResult = compileVPPs(VP.blocks, VP.kids, form, VP.king); + return { + ps: combineLengths ? flattenLengths(psResult) : psResult, + // TODO: English doesn't quite work for dynamic compounds in passive voice + e: (verb.voice === "passive" && VP.isCompound === "dynamic") ? undefined : compileEnglishVP(VP), + }; +} + +function compileVPPs(blocks: T.Block[][], kids: T.Kid[], form: T.FormVersion, king: "subject" | "object"): T.SingleOrLengthOpts { + if (hasVerbWithLengths(blocks)) { + return { + long: compileVPPs(specifyVerbLength(blocks, "long"), kids, form, king) as T.PsString[], + short: compileVPPs(specifyVerbLength(blocks, "short"), kids, form, king) as T.PsString[], + }; + } + const subjectPerson = getSubjectSelectionFromBlocks(blocks) + .selection.selection.person; + const blocksWKids = putKidsInKidsSection( + filterForVisibleBlocksVP(blocks, form, king), + kids, + false, + ); + return removeDuplicates(combineIntoText(blocksWKids, subjectPerson, {})); +} + +function compileEPPs(blocks: T.Block[][], kids: T.Kid[], omitSubject: boolean, blankOut?: BlankoutOptions): T.SingleOrLengthOpts { if (hasEquativeWithLengths(blocks)) { return { long: compileEPPs(specifyEquativeLength(blocks, "long"), kids, omitSubject, blankOut) as T.PsString[], @@ -280,31 +106,74 @@ function compileEPPs(blocks: T.Block[], kids: T.Kid[], omitSubject: boolean, bla const subjectPerson = getSubjectSelectionFromBlocks(blocks) .selection.selection.person; const blocksWKids = putKidsInKidsSection( - omitSubject ? blocks.filter(b => b.type !== "subjectSelection") : blocks, + omitSubject ? blocks.map(blks => blks.filter(b => b.type !== "subjectSelection")) : blocks, kids, !!blankOut?.kidsSection ); - // BIG TODO: If the kid's section is blank and there are no kids - add a blank for the kids section! return removeDuplicates(combineIntoText(blocksWKids, subjectPerson, blankOut)); } -function combineIntoText(pieces: (T.Block | T.Kid | T.PsString)[], subjectPerson: T.Person, blankOut?: BlankoutOptions): T.PsString[] { - const first = pieces[0]; - const rest = pieces.slice(1); - const firstPs = ("p" in first) - ? [first] - : (blankOut?.equative && first.type === "equative") - ? [blank] - : ((blankOut?.ba) && first.type === "ba") - ? [kidsBlank] - : getPsFromPiece(first, subjectPerson); - if (!rest.length) { - return firstPs; +export function filterForVisibleBlocksVP(blocks: T.Block[][], form: T.FormVersion, king: "subject" | "object"): T.Block[][] { + const servant = king === "object" ? "subject" : "object"; + return blocks.map(blks => blks.filter((block) => { + if (form.removeKing) { + if ( + (king === "subject" && block.type === "subjectSelection") + || + (king === "object" && block.type === "objectSelection") + ) return false; + } + if (form.shrinkServant) { + if ( + (servant === "subject" && block.type === "subjectSelection") + || + (servant === "object" && block.type === "objectSelection") + ) return false; + } + if (block.type === "objectSelection" && typeof block.selection !== "object") { + return false; + } + return true; + })); +} + +export function filterForVisibleBlocksEP(blocks: T.Block[][], omitSubject: boolean): T.Block[][] { + if (!omitSubject) return blocks; + return blocks.map(blks => blks.filter((block) => { + if (block.type === "subjectSelection") { + return false; + } + return true; + })); +} + +function combineIntoText(piecesWVars: (T.Block | T.Kid | T.PsString)[][], subjectPerson: T.Person, blankOut?: BlankoutOptions): T.PsString[] { + function combine(pieces: (T.Block | T.Kid | T.PsString)[]): T.PsString[] { + const first = pieces[0]; + const next = pieces[1]; + const rest = pieces.slice(1); + const firstPs = ("p" in first) + ? [first] + : (blankOut?.equative && first.type === "equative") + ? [blank] + : ((blankOut?.ba) && first.type === "ba") + ? [kidsBlank] + : getPsFromPiece(first, subjectPerson); + if (!rest.length) { + return firstPs; + } + return combine(rest).flatMap(r => ( + firstPs.map(fPs => concatPsString( + fPs, + (!("p" in first) && first.type === "perfectiveHead" && !("p" in next) && (next.type === "verb" || next.type === "negative" || next.type === "mini-pronoun")) + ? ((next.type === "negative" || next.type === "mini-pronoun") ? { p: "", f: "-" } : "") + : " ", + r, + )) + ) + ); } - return combineIntoText(rest, subjectPerson, blankOut).flatMap(r => ( - firstPs.map(fPs => concatPsString(fPs, " ", r)) - ) - ); + return piecesWVars.flatMap(combine); } function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsString[] { @@ -314,8 +183,10 @@ function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsSt if (piece.type === "mini-pronoun") { return [piece.ps]; } - if (piece.type === "nu") { - return [{ p: "نه", f: "nú" }]; + if (piece.type === "negative") { + return [ + negativeParticle[piece.imperative ? "imperative" : "nonImperative"], + ]; } if (piece.type === "equative") { // length will already be specified in compileEPPs - this is just for type safety @@ -324,12 +195,45 @@ function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsSt if (piece.type === "subjectSelection" || piece.type === "predicateSelection") { return getPashtoFromRendered(piece.selection, subjectPerson); } - // if (piece.type === "AP") { + if (piece.type === "AP") { return getPashtoFromRendered(piece, subjectPerson); - // } + } + if (piece.type === "perfectiveHead") { + return [piece.ps]; + } + if (piece.type === "verbComplement") { + return [{ p: "---", f: "---"}]; //getPashtoFromRendered(piece.complement); + } + if (piece.type === "objectSelection") { + if (typeof piece.selection !== "object") { + return [{ p: "", f: "" }]; + } + return getPashtoFromRendered(piece.selection, subjectPerson); + } + if (piece.type === "verb") { + // getLong is just for type safety - we will have split up the length options earlier in compileVPPs + return getLong(piece.block.ps); + } + if (piece.type === "perfectParticipleBlock") { + // getLong is just for type safety - we will have split up the length options earlier in compileVPPs + return getLong(piece.ps); + } + if (piece.type === "perfectEquativeBlock") { + // just using the short one for now - it will only be short anyways + return getShort(piece.ps); + } + if (piece.type === "modalVerbBlock") { + // getLong is just for type safety - we will have split up the length options earlier in compileVPPs + return getLong(piece.ps); + } + if (piece.type === "modalVerbKedulPart") { + // just using the short one for now - it will only be short anyways + return getShort(piece.ps); + } + throw new Error("unrecognized piece type"); } -function getEngAPs(blocks: T.Block[]): string { +function getEngAPs(blocks: T.Block[][]): string { return getAPsFromBlocks(blocks).reduce((accum, curr) => { const e = getEnglishFromRendered(curr); if (!e) return accum; @@ -337,76 +241,31 @@ function getEngAPs(blocks: T.Block[]): string { }, ""); } -function getEnglishAPs(blocks: (T.Rendered | T.Rendered | T.RenderedVPSBlock)[]) { - const APs = blocks.filter(x => x.type !== "subjectSelection") as T.Rendered[]; - return APs.reduce((accum, curr) => { - const e = getEnglishFromRendered(curr); - if (!e) return accum; - return `${accum} ${e}`; - }, ""); -} - -function putKidsInKidsSection(blocks: T.Block[], kids: T.Kid[], enforceKidsSectionBlankout: boolean): (T.Block | T.Kid | T.PsString)[] { - const first = blocks[0]; - const rest = blocks.slice(1); - return [ - first, - ...enforceKidsSectionBlankout ? [kidsBlank] : kids, - ...rest, - ]; -} - - -function mergeSegments(s1: Segment, s2: Segment, noSpace?: "no space"): Segment { - if (noSpace) { - return s2.adjust({ ps: (p) => concatPsString(s1.ps[0], p) }); +function putKidsInKidsSection(blocksWVars: T.Block[][], kids: T.Kid[], enforceKidsSectionBlankout: boolean): (T.Block | T.Kid | T.PsString)[][] { + function insert(blocks: T.Block[]): (T.Block | T.Kid | T.PsString)[] { + const first = blocks[0]; + const rest = blocks.slice(1); + return [ + first, + ...enforceKidsSectionBlankout ? [kidsBlank] : kids, + ...rest, + ]; } - 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; + return blocksWVars.map(insert); } function compileEnglishVP(VP: T.VPRendered): string[] | undefined { function insertEWords(e: string, { subject, object, APs }: { subject: string, object?: string, APs: string }): string { return e.replace("$SUBJ", subject).replace("$OBJ", object || "") + APs; } - const engSubj = getRenderedSubjectSelection(VP.blocks).selection; - const obj = getRenderedObjectSelection(VP.blocks).selection; + const engSubj = getSubjectSelectionFromBlocks(VP.blocks).selection; + const obj = getObjectSelectionFromBlocks(VP.blocks).selection; const engObj = typeof obj === "object" ? obj : obj === "none" ? "" : undefined; - const engAPs = getEnglishAPs(VP.blocks); + const engAPs = getEngAPs(VP.blocks); // require all English parts for making the English phrase return (VP.englishBase && engSubj && engObj !== undefined) ? VP.englishBase.map(e => insertEWords(e, { @@ -438,27 +297,7 @@ function compileEnglishEP(EP: T.EPRendered): string[] | undefined { return b; } -export function orderKidsSection(kids: Segment[]): Segment[] { - const sorted = [...kids]; - return sorted.sort((a, b) => { - // ba first - if (a.isBa) return -1; - // kinds lined up 1st 2nd 3rd person - if (a.isMiniPronoun && b.isMiniPronoun) { - if (a.isMiniPronoun < b.isMiniPronoun) { - return -1; - } - if (a.isMiniPronoun > b.isMiniPronoun) { - return 1; - } - // TODO: is this enough? - return 0; - } - return 0; - }); -} - -export function checkEPForMiniPronounsError(s: T.EPSelectionState): undefined | string { +export function checkForMiniPronounsError(s: T.EPSelectionState | T.VPSelectionState): undefined | string { function findDuplicateMiniP(mp: T.MiniPronoun[]): T.MiniPronoun | undefined { const duplicates = mp.filter((item, index) => ( mp.findIndex(m => item.ps.p === m.ps.p) !== index @@ -467,10 +306,14 @@ export function checkEPForMiniPronounsError(s: T.EPSelectionState): undefined | return duplicates[0]; } const kids = (() => { - const EPS = completeEPSelection(s); - if (!EPS) return undefined; - const { kids } = renderEP(EPS); - return kids; + if ("predicate" in s) { + const EPS = completeEPSelection(s); + if (!EPS) return undefined; + return renderEP(EPS).kids; + }; + const VPS = completeVPSelection(s); + if (!VPS) return undefined; + return renderVP(VPS).kids; })(); if (!kids) return undefined; const miniPronouns = kids.filter(x => x.type === "mini-pronoun") as T.MiniPronoun[]; @@ -484,67 +327,6 @@ export function checkEPForMiniPronounsError(s: T.EPSelectionState): undefined | return undefined; } -export function checkForMiniPronounsError(s: T.VPSelectionState): undefined | string { - function findDuplicateMiniPronoun(mp: Segment[]): Segment | undefined { - const duplicates = mp.filter((item, index) => ( - mp.findIndex(m => item.ps[0].p === m.ps[0].p) !== index - )); - if (duplicates.length === 0) return undefined; - return duplicates[0]; - } - const kids = (() => { - const VPS = completeVPSelection(s); - if (!VPS) return undefined; - const { kids } = getVPSegmentsAndKids(renderVP(VPS)); - return kids; - })(); - if (!kids) return undefined; - const miniPronouns = kids.filter(x => x.isMiniPronoun); - if (miniPronouns.length > 2) { - return "can't add another mini-pronoun, there are alread two"; - } - const duplicateMiniPronoun = findDuplicateMiniPronoun(miniPronouns); - if (duplicateMiniPronoun) { - return `there's already a ${duplicateMiniPronoun.ps[0].p} - ${duplicateMiniPronoun.ps[0].f} mini-pronoun in use, can't have two of those`; - } - return undefined; -} - -export function findPossesivesToShrinkInVP(VP: T.VPRendered, f: { - shrunkServant: boolean, - removedKing: boolean, -}): T.Rendered[] { - return VP.blocks.reduce((found, block) => { - if (block.type === "subjectSelection") { - if (block.selection.selection.role === "king" && f.removedKing) return found; - if (block.selection.selection.role === "servant" && f.shrunkServant) return found; - return [ - ...findPossesivesInNP(block.selection), - ...found, - ]; - } - if (block.type === "objectSelection") { - if (typeof block.selection !== "object") return found; - if (block.selection.selection.role === "king" && f.removedKing) return found; - if (block.selection.selection.role === "servant" && f.shrunkServant) return found; - return [ - ...findPossesivesInNP(block.selection), - ...found, - ]; - } - if (block.selection.type === "sandwich") { - if (block.selection.inside.selection.type === "pronoun") { - return found; - } - return [ - ...findPossesivesInNP(block.selection.inside), - ...found, - ]; - } - return found; - }, [] as T.Rendered[]); -} - function findPossesivesInNP(NP: T.Rendered | T.ObjectNP | undefined): T.Rendered[] { if (NP === undefined) return []; if (typeof NP !== "object") return []; @@ -574,24 +356,13 @@ function findPossesivesInAdjective(a: T.Rendered): T.Rende return findPossesivesInNP(a.sandwich.inside); } - -// export function findPossesiveToShrinkInVP(VP: T.VPRendered): T.Rendered[] { -// const obj: T.Rendered | undefined = ("object" in VP && typeof VP.object === "object") -// ? VP.object -// : undefined; -// return [ -// ...findPossesivesInNP(VP.subject), -// ...findPossesivesInNP(obj), -// ]; -// } - -export function shrinkNP(np: T.Rendered): Segment { - function getFirstSecThird(): 1 | 2 | 3 { - if ([0, 1, 6, 7].includes(np.selection.person)) return 1; - if ([2, 3, 8, 9].includes(np.selection.person)) return 2; - return 3; +export function flattenLengths(r: T.SingleOrLengthOpts): T.PsString[] { + if ("long" in r) { + return Object.values(r).flat(); } - const [row, col] = getVerbBlockPosFromPerson(np.selection.person); - return makeSegment(pronouns.mini[row][col], ["isKid", getFirstSecThird()]); + if (Array.isArray(r)) { + return r; + } + return [r]; } diff --git a/src/lib/phrase-building/render-common.ts b/src/lib/phrase-building/render-common.ts new file mode 100644 index 0000000..237bd41 --- /dev/null +++ b/src/lib/phrase-building/render-common.ts @@ -0,0 +1,108 @@ +import * as T from "../../types"; +import { + getPersonFromNP, +} from "./vp-tools"; +import { pronouns } from "../grammar-units"; +import { getVerbBlockPosFromPerson } from "../misc-helpers"; +import { getFirstSecThird } from "../../lib/misc-helpers"; + +export function findPossesivesToShrink( + blocks: (T.EPSBlockComplete | T.VPSBlockComplete | T.SubjectSelectionComplete | T.PredicateSelectionComplete | T.APSelection)[], +): T.MiniPronoun[] { + return blocks.reduce((kids, item) => { + const block = "block" in item ? item.block : item; + if (block.type === "subjectSelection") { + return [ + ...kids, + ...findShrunkenPossInNP(block.selection), + ]; + } + if (block.type === "objectSelection") { + if (typeof block.selection !== "object") return kids; + return [ + ...kids, + ...findShrunkenPossInNP(block.selection), + ]; + } + if (block.type === "AP") { + if (block.selection.type === "adverb") return kids; + return [ + ...kids, + ...findShrunkenPossInNP(block.selection.inside), + ]; + } + if (block.type === "predicateSelection") { + if (block.selection.type === "EQComp") { + if (block.selection.selection.type === "sandwich") { + return [ + ...kids, + ...findShrunkenPossInNP(block.selection.selection.inside), + ]; + } + return kids; + } + return [ + ...kids, + ...findShrunkenPossInNP(block.selection), + ]; + } + return kids; + }, [] as T.MiniPronoun[]); +} + +function findShrunkenPossInNP(NP: T.NPSelection): T.MiniPronoun[] { + if (NP.selection.type === "pronoun") return []; + if (!NP.selection.possesor) return []; + // if (NP.selection.type === "noun") { + // if (NP.selection.adjectives) { + // const { adjectives, ...rest } = NP.selection; + // return [ + // // TODO: ability to find possesives shrinkage in sandwiches in adjectives + // // ...findShrunkenPossInAdjectives(adjectives), + // ...findShrunkenPossInNP({ type: "NP", selection: { + // ...rest, + // adjectives: [], + // }}), + // ]; + // } + // } + if (NP.selection.possesor.shrunken) { + const person = getPersonFromNP(NP.selection.possesor.np); + const miniP: T.MiniPronoun = { + type: "mini-pronoun", + person, + ps: getMiniPronounPs(person), + source: "possesive", + np: NP.selection.possesor.np, + }; + return [miniP]; + } + return findShrunkenPossInNP(NP.selection.possesor.np); +} + +export function getMiniPronounPs(person: T.Person): T.PsString { + const [row, col] = getVerbBlockPosFromPerson(person); + return pronouns.mini[row][col][0]; +} + +export function orderKids(kids: T.Kid[]): T.Kid[] { + const sorted = [...kids].sort((a, b) => { + // ba first + if (a.type === "ba") return -1; + // kinds lined up 1st 2nd 3rd person + if (a.type === "mini-pronoun" && b.type === "mini-pronoun") { + const aPers = getFirstSecThird(a.person); + const bPers = getFirstSecThird(b.person); + if (aPers < bPers) { + return -1; + } + if (aPers > bPers) { + return 1; + } + // TODO: is this enough? + return 0; + } + return 0; + }); + return sorted; +} \ No newline at end of file diff --git a/src/lib/phrase-building/render-ep.ts b/src/lib/phrase-building/render-ep.ts index c6b1b30..f9cff82 100644 --- a/src/lib/phrase-building/render-ep.ts +++ b/src/lib/phrase-building/render-ep.ts @@ -4,7 +4,7 @@ import { getPersonFromNP, } from "./vp-tools"; import { renderNPSelection } from "./render-np"; -import { getFirstSecThird, getPersonFromVerbForm } from "../../lib/misc-helpers"; +import { getPersonFromVerbForm } from "../../lib/misc-helpers"; import { getVerbBlockPosFromPerson } from "../misc-helpers"; import { getEnglishWord } from "../get-english-word"; import { psStringFromEntry } from "../p-text-helpers"; @@ -13,7 +13,7 @@ import { renderAdjectiveSelection } from "./render-adj"; import { renderSandwich } from "./render-sandwich"; import { EPSBlocksAreComplete, getSubjectSelection } from "./blocks-utils"; import { removeAccentsWLength } from "../accent-helpers"; -import { pronouns } from "../grammar-units"; +import { findPossesivesToShrink, orderKids } from "./render-common"; export function renderEP(EP: T.EPSelectionComplete): T.EPRendered { const { kids, blocks, englishEquativePerson } = getEPSBlocksAndKids(EP); @@ -29,7 +29,7 @@ export function renderEP(EP: T.EPSelectionComplete): T.EPRendered { }; } -function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks: T.Block[], englishEquativePerson: T.Person } { +function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks: T.Block[][], englishEquativePerson: T.Person } { const subject = getSubjectSelection(EP.blocks).selection; const subjectPerson = getPersonFromNP(subject); const commandingNP: T.NPSelection = subject.selection.type === "pronoun" @@ -39,7 +39,7 @@ function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks : subject; const commandingPerson = getPersonFromNP(commandingNP); const equative: T.EquativeBlock = { type: "equative", equative: renderEquative(EP.equative, commandingPerson) }; - const blocks: T.Block[] = [ + const blocks: T.Block[][] = insertNegative([ ...renderEPSBlocks(EP.blocks), { type: "predicateSelection", @@ -47,10 +47,9 @@ function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks ? renderNPSelection(EP.predicate.selection, false, false, "subject", "king") : renderEqCompSelection(EP.predicate.selection, commandingPerson), }, - ...EP.equative.negative ? [{ type: "nu" } as T.Block] : [], - EP.equative.negative ? removeAccontsFromEq(equative) : equative, - ]; - const miniPronouns = findPossesivesToShrink([...EP.blocks, EP.predicate], EP.omitSubject); + equative, + ], EP.equative.negative); + const miniPronouns = findPossesivesToShrink(removeOrKeepSubject([...EP.blocks, EP.predicate], EP.omitSubject)); const kids: T.Kid[] = orderKids([ ...equative.equative.hasBa ? [{ type: "ba" } as T.Kid] : [], ...miniPronouns, @@ -64,92 +63,27 @@ function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks }; } -function orderKids(kids: T.Kid[]): T.Kid[] { - const sorted = [...kids].sort((a, b) => { - // ba first - if (a.type === "ba") return -1; - // kinds lined up 1st 2nd 3rd person - if (a.type === "mini-pronoun" && b.type === "mini-pronoun") { - const aPers = getFirstSecThird(a.person); - const bPers = getFirstSecThird(b.person); - if (aPers < bPers) { - return -1; - } - if (aPers > bPers) { - return 1; - } - // TODO: is this enough? - return 0; - } - return 0; - }); - return sorted; +function insertNegative(blocks: T.Block[], negative: boolean): T.Block[][] { + if (!negative) return [blocks]; + const blocksA = removeAccentsFromEq(blocks); + return [ + [ + ...blocksA.slice(0, blocks.length - 1), + { type: "negative", imperative: false }, + ...blocksA.slice(-1), // last (equative) + ], + [ + ...blocksA.slice(0, blocks.length - 2), + { type: "negative", imperative: false }, + ...blocksA.slice(-1), // last (equative) + ...blocksA.slice(-2, -1), // second last (predicate) + ], + ]; } -function findPossesivesToShrink(blocks: (T.EPSBlockComplete | T.SubjectSelectionComplete | T.PredicateSelectionComplete | T.APSelection)[], omitSubject: boolean): T.MiniPronoun[] { - return blocks.reduce((kids, item) => { - const block = "block" in item ? item.block : item; - if (block.type === "subjectSelection") { - if (omitSubject) return kids; - return [ - ...kids, - ...findShrunkenPossInNP(block.selection), - ]; - } - if (block.type === "AP") { - if (block.selection.type === "adverb") return kids; - return [ - ...kids, - ...findShrunkenPossInNP(block.selection.inside), - ]; - } - if (block.type === "predicateSelection") { - if (block.selection.type === "EQComp") { - if (block.selection.selection.type === "sandwich") { - return [ - ...kids, - ...findShrunkenPossInNP(block.selection.selection.inside), - ]; - } - return kids; - } - return [ - ...kids, - ...findShrunkenPossInNP(block.selection), - ]; - } - return kids; - }, [] as T.MiniPronoun[]); -} - -function findShrunkenPossInNP(NP: T.NPSelection): T.MiniPronoun[] { - if (NP.selection.type === "pronoun") return []; - if (!NP.selection.possesor) return []; - // if (NP.selection.type === "noun") { - // if (NP.selection.adjectives) { - // const { adjectives, ...rest } = NP.selection; - // return [ - // // TODO: ability to find possesives shrinkage in sandwiches in adjectives - // // ...findShrunkenPossInAdjectives(adjectives), - // ...findShrunkenPossInNP({ type: "NP", selection: { - // ...rest, - // adjectives: [], - // }}), - // ]; - // } - // } - if (NP.selection.possesor.shrunken) { - const person = getPersonFromNP(NP.selection.possesor.np); - const miniP: T.MiniPronoun = { - type: "mini-pronoun", - person, - ps: getMiniPronounPs(person), - source: "possesive", - np: NP.selection.possesor.np, - }; - return [miniP]; - } - return findShrunkenPossInNP(NP.selection.possesor.np); +function removeOrKeepSubject(blocks: (T.EPSBlockComplete | T.SubjectSelectionComplete | T.PredicateSelectionComplete | T.APSelection)[], omitSubject: boolean): (T.EPSBlockComplete | T.SubjectSelectionComplete | T.PredicateSelectionComplete | T.APSelection)[] { + if (!omitSubject) return blocks; + return blocks.filter(b => !("type" in b && b.type === "subjectSelection")); } export function getEquativeForm(tense: T.EquativeTense): { hasBa: boolean, form: T.SingleOrLengthOpts } { @@ -343,17 +277,18 @@ export function completeEPSelection(eps: T.EPSelectionState): T.EPSelectionCompl }; } -export function getMiniPronounPs(person: T.Person): T.PsString { - const [row, col] = getVerbBlockPosFromPerson(person); - return pronouns.mini[row][col][0]; -} - -function removeAccontsFromEq(equ: T.EquativeBlock): T.EquativeBlock { - return { - ...equ, - equative: { - ...equ.equative, - ps: removeAccentsWLength(equ.equative.ps), - }, - }; +function removeAccentsFromEq(blocks: T.Block[]): T.Block[] { + return blocks.map((block) => { + if (block.type === "equative") { + const e: T.EquativeBlock = { + ...block, + equative: { + ...block.equative, + ps: removeAccentsWLength(block.equative.ps), + }, + }; + return e; + } + return block; + }); } \ No newline at end of file diff --git a/src/lib/phrase-building/render-np.ts b/src/lib/phrase-building/render-np.ts index e41cbd3..95c1e7e 100644 --- a/src/lib/phrase-building/render-np.ts +++ b/src/lib/phrase-building/render-np.ts @@ -17,8 +17,8 @@ import { renderAdjectiveSelection } from "./render-adj"; import { isPattern5Entry, isAnimNounEntry } from "../type-predicates"; export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflectEnglish: boolean, role: "subject", soRole: "servant" | "king" | "none"): T.Rendered; -export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "object", soRole: "servant" | "king" | "none"): T.Rendered | T.Person.ThirdPlurMale | "none"; -export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "subject" | "object", soRole: "servant" | "king" | "none"): T.Rendered | T.Person.ThirdPlurMale | "none" { +export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflectEnglish: boolean, role: "object", soRole: "servant" | "king" | "none"): T.Rendered; +export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflectEnglish: boolean, role: "subject" | "object", soRole: "servant" | "king" | "none"): T.Rendered { if (typeof NP !== "object") { if (role !== "object") { throw new Error("ObjectNP only allowed for objects"); diff --git a/src/lib/phrase-building/render-vp.ts b/src/lib/phrase-building/render-vp.ts index 34463f6..5e9c3f5 100644 --- a/src/lib/phrase-building/render-vp.ts +++ b/src/lib/phrase-building/render-vp.ts @@ -7,8 +7,10 @@ import { hasBaParticle, getLong, isImperativeBlock, + splitOffLeapfrogWordFull, + getShort, } from "../p-text-helpers"; -import { removeAccents } from "../accent-helpers"; +import { removeAccents, removeAccentsWLength } from "../accent-helpers"; import { getPersonFromNP, removeBa, @@ -16,6 +18,8 @@ import { getTenseVerbForm, } from "./vp-tools"; import { + isImperativeTense, + isModalTense, isPattern4Entry, isPerfectTense, } from "../type-predicates"; @@ -24,6 +28,7 @@ import { personGender } from "../../lib/misc-helpers"; import { renderNPSelection } from "./render-np"; import { getObjectSelection, getSubjectSelection } from "./blocks-utils"; import { renderAPSelection } from "./render-ap"; +import { findPossesivesToShrink, orderKids, getMiniPronounPs } from "./render-common"; // TODO: ISSUE GETTING SPLIT HEAD NOT MATCHING WITH FUTURE VERBS @@ -47,6 +52,12 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered { const inflectSubject = isPast && isTransitive && !isMascSingAnimatePattern4(subject); const inflectObject = !isPast && isFirstOrSecondPersPronoun(object); // Render Elements + const firstBlocks = renderVPBlocks(VP.blocks, { + inflectSubject, + inflectObject, + king, + }); + const { verbBlocks, hasBa } = renderVerbSelection(VP.verb, kingPerson, objectPerson); const b: T.VPRendered = { type: "VPRendered", king, @@ -54,12 +65,11 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered { isPast, isTransitive, isCompound: VP.verb.isCompound, - blocks: renderVPBlocks(VP.blocks, { - inflectSubject, - inflectObject, - king, - }), - verb: renderVerbSelection(VP.verb, kingPerson, objectPerson), + blocks: insertNegative([ + ...firstBlocks, + ...verbBlocks, + ], VP.verb.negative, isImperativeTense(VP.verb.tense)), + kids: getVPKids(hasBa, VP.blocks, VP.form, king), englishBase: renderEnglishVPBase({ subjectPerson, object: VP.verb.isCompound === "dynamic" ? "none" : object, @@ -71,28 +81,271 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered { return b; } +// function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[], V: T.VerbRendered): Segment[][] { +// const hasLeapfrog = isPerfectTense(V.tense) || isModalTense(V.tense); +// 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 = isImperativeTense(V.tense) +// ? { p: "مه", f: "mú" } +// : { p: "نه", f: "nú" }; +// if (!headSegment) { +// if ("front" in rest) { +// return [ +// // pefect nu dey me leeduley and nu me dey leeduley +// // actually don't think this is correct - keeping it out for now +// // [ +// // 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 getVPKids(hasBa: boolean, blocks: T.VPSBlockComplete[], form: T.FormVersion, king: "subject" | "object"): T.Kid[] { + const subject = getSubjectSelection(blocks).selection; + const objectS = getObjectSelection(blocks).selection; + const object = typeof objectS === "object" ? objectS : undefined; + const servantNP = king === "subject" ? object : subject; + const shrunkenServant = (form.shrinkServant && servantNP) + ? shrinkServant(servantNP) + : undefined; + const shrunkenPossesives = findPossesivesToShrink(removeAbbreviated(blocks, form, king)); + return orderKids([ + ...hasBa ? [{ type: "ba" } as T.Kid] : [], + ...shrunkenServant ? [shrunkenServant] : [], + ...shrunkenPossesives ? shrunkenPossesives : [], + ]); +} + +function removeAbbreviated(blocks: T.VPSBlockComplete[], form: T.FormVersion, king: "subject" | "object"): T.VPSBlockComplete[] { + return blocks.filter(({ block }) => { + if (block.type === "subjectSelection") { + if (form.shrinkServant && king === "object") return false; + if (form.removeKing && king === "subject") return false; + } + if (block.type === "objectSelection") { + if (form.shrinkServant && king === "subject") return false; + if (form.removeKing && king === "object") return false; + } + return true; + }) +} + +function insertNegative(blocks: T.Block[], negative: boolean, imperative: boolean): T.Block[][] { + // TODO: proper arrange verb with negative and variations + // ALSO removing the accent from nu + if (!negative) return [blocks]; + const blocksA = removeVerbAccent(blocks); + const basic: T.Block[] = [ + ...blocksA.slice(0, blocks.length - 1), + { + type: "negative", + imperative, + }, + ...blocksA.slice(-1), + ]; + if (hasNonStandardPerfectiveSplit(blocks)) { + return [ + basic, + [ + ...blocksA.slice(0, blocks.length - 2), + { type: "negative", imperative }, + ...blocksA.slice(-2, -1), // second last (perfective split) + ...blocksA.slice(-1), // last (verb) + ], + ]; + } + if (hasLeapFroggable(blocks)) { + return [ + [ + ...blocksA.slice(0, blocks.length - 2), + { type: "negative", imperative }, + ...blocksA.slice(-1), // last + ...blocksA.slice(-2, -1), // second last + ], + basic, + ]; + } + return [basic]; +} + +function hasLeapFroggable(blocks: T.Block[]): boolean { + return blocks.some(b => b.type === "perfectEquativeBlock" || b.type === "modalVerbBlock"); +} + +function hasNonStandardPerfectiveSplit(blocks: T.Block[]): boolean { + const perfS = blocks.find(b => b.type === "perfectiveHead"); + if (!perfS || perfS.type !== "perfectiveHead") { + return false; + } + return !["و", "وا"].includes(perfS.ps.p); +} + +function removeVerbAccent(blocks: T.Block[]): T.Block[] { + return blocks.map((block) => { + if (block.type === "perfectiveHead") { + return { + ...block, + ps: removeAccents(block.ps), + }; + } + if (block.type === "verb") { + return { + ...block, + block: { + ...block.block, + ps: removeAccentsWLength(block.block.ps), + }, + }; + } + return block; + }); +} + +function shrinkServant(np: T.NPSelection): T.MiniPronoun { + const person = getPersonFromNP(np); + return { + type: "mini-pronoun", + person, + ps: getMiniPronounPs(person), + source: "servant", + np, + }; +} + function renderVPBlocks(blocks: T.VPSBlockComplete[], config: { inflectSubject: boolean, inflectObject: boolean, king: "subject" | "object", -}): T.RenderedVPSBlock[] { - return blocks.map(({ block }): T.RenderedVPSBlock => { +}): T.Block[] { + return blocks.reduce((blocks, { block }): T.Block[] => { if (block.type === "subjectSelection") { - return { - type: "subjectSelection", - selection: renderNPSelection(block.selection, config.inflectSubject, false, "subject", config.king === "subject" ? "king" : "servant"), - } + return [ + ...blocks, + { + type: "subjectSelection", + selection: renderNPSelection(block.selection, config.inflectSubject, false, "subject", config.king === "subject" ? "king" : "servant"), + }, + ]; } if (block.type === "objectSelection") { const object = typeof block === "object" ? block.selection : block; + if (typeof object !== "object") { + return [ + ...blocks, + { + type: "objectSelection", + selection: object, + }, + ]; + } const selection = renderNPSelection(object, config.inflectObject, true, "object", config.king === "object" ? "king" : "servant"); - return { - type: "objectSelection", - selection, - }; + return [ + ...blocks, + { + type: "objectSelection", + selection, + }, + ]; } - return renderAPSelection(block); - }); + return [ + ...blocks, + renderAPSelection(block), + ]; + }, [] as T.Block[]); } function whatsAdjustable(VP: T.VPSelectionComplete): "both" | "king" | "servant" { @@ -109,7 +362,15 @@ function whatsAdjustable(VP: T.VPSelectionComplete): "both" | "king" | "servant" : "king"; } -function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): T.VerbRendered { +type VerbBlocks = | [T.PerfectiveHeadBlock, T.VerbRenderedBlock] // verb w perfective split + | [T.VerbRenderedBlock] // verb w/out perfective split + | [T.PerfectParticipleBlock, T.PerfectEquativeBlock] // perfect verb + | [T.ModalVerbBlock, T.ModalVerbKedulPart] // modal verb + +function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): { + verbBlocks: VerbBlocks + hasBa: boolean, +} { const v = vs.dynAuxVerb || vs.verb; const conjugations = conjugateVerb(v.entry, v.complement); // TODO: error handle this? @@ -118,14 +379,73 @@ function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, obje // will default to transitive ? conjugations.transitive : "stative" in conjugations - // TODO: option to manually select stative/dynamic ? conjugations.stative - : conjugations; + : conjugations; + const { ps: { head, rest }, hasBa } = getPsVerbConjugation(conj, vs, person, objectPerson); + const vrb: T.VerbRenderedBlock = { + type: "verb", + block: { + ...vs, + ps: rest, + person, + hasBa, + }, + }; + const verbBlocks = [ + ...(head ? ( + vs.isCompound === "stative" ? [{ + type: "verbComplement", + complement: head, + } as T.VerbComplementBlock] : [{ + type: "perfectiveHead", + ps: head, + } as T.PerfectiveHeadBlock] + ) : [] as [T.VerbComplementBlock] | [T.PerfectiveHeadBlock] | []), + ...splitUpIfModal(vrb), + ] as VerbBlocks; + const perfectStuff = isPerfectTense(vrb.block.tense) ? getPerfectStuff(rest, vrb) : undefined; return { - ...vs, - person, - ...getPsVerbConjugation(conj, vs, person, objectPerson), + verbBlocks: perfectStuff ? perfectStuff : verbBlocks, + hasBa, + }; +} + +function splitUpIfModal(v: T.VerbRenderedBlock): [T.VerbRenderedBlock] | [T.ModalVerbBlock, T.ModalVerbKedulPart] { + if (!isModalTense(v.block.tense)) { + return [v]; } + const [vrb, k] = splitOffLeapfrogWordFull(v.block.ps); + return [ + { + type: "modalVerbBlock", + ps: vrb, + verb: v, + }, + { + type: "modalVerbKedulPart", + // sadly just for type safety - the conjugator always just gives us the short form + ps: getShort(k), + verb: v, + }, + ]; +} + +function getPerfectStuff(v: T.SingleOrLengthOpts, vrb: T.VerbRenderedBlock): [T.PerfectParticipleBlock, T.PerfectEquativeBlock] { + const [p, eq] = splitOffLeapfrogWordFull(v); + return [ + { + type: "perfectParticipleBlock", + ps: p, + person: vrb.block.person, + verb: vrb, + }, + { + type: "perfectEquativeBlock", + // TODO: right now the conjugator just always spits out the short form of the equative - would be nice to have both + ps: getShort(eq), + person: vrb.block.person, + }, + ]; } function getPsVerbConjugation(conj: T.VerbConjugation, vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): { @@ -156,7 +476,20 @@ function getPsVerbConjugation(conj: T.VerbConjugation, vs: T.VerbSelectionComple ps, }; } - return { hasBa, ps: { head: undefined, rest: verbForm }}; + return { hasBa, ps: { head: undefined, rest: removeBaFromForm(verbForm) }}; +} + +function removeBaFromForm(f: T.SingleOrLengthOpts): T.SingleOrLengthOpts { + if ("long" in f) { + return { + long: removeBaFromForm(f.long) as T.PsString[], + short: removeBaFromForm(f.short) as T.PsString[], + ...f.mini ? { + mini: removeBaFromForm(f.mini) as T.PsString[], + } : {}, + }; + } + return f.map(removeBa); } function getVerbFromBlock(block: T.SingleOrLengthOpts, person: T.Person): T.SingleOrLengthOpts { diff --git a/src/lib/phrase-building/segment.ts b/src/lib/phrase-building/segment.ts deleted file mode 100644 index ecc248c..0000000 --- a/src/lib/phrase-building/segment.ts +++ /dev/null @@ -1,120 +0,0 @@ -import * as T from "../../types"; -import { - makePsString, -} from "../accent-and-ps-utils"; -import { - concatPsString, -} from "../p-text-helpers"; - -type SegmentDescriptions = { - isVerbHead?: boolean, - isOoOrWaaHead?: boolean, - isVerbRest?: boolean, - isMiniPronoun?: number, - isKid?: boolean, - // TODO: Simplify to just isKidAfterHead? - isKidBetweenHeadAndRest?: boolean, - isNu?: boolean, - isBa?: boolean, -} - -type SDT = keyof SegmentDescriptions; -export type Segment = { ps: T.PsString[] } & SegmentDescriptions & { - adjust: (o: { ps?: T.PsString | T.PsString[] | ((ps: T.PsString) => T.PsString), desc?: SDT[] }) => Segment, -}; - - -export function makeSegment( - input: T.PsString | T.PsString[], - options?: (keyof SegmentDescriptions | 1 | 2 | 3)[], -): Segment { - const ps: T.PsString[] = Array.isArray(input) - ? input - : [input]; - return { - ps: Array.isArray(ps) ? ps : [ps], - ...options && options.reduce((all, curr) => ({ - ...all, - ...typeof curr === "number" ? { - isMiniPronoun: curr, - } : { - [curr]: true, - }, - }), {}), - adjust: function(o): Segment { - return { - ...this, - ...o.ps ? { - // TODO: is this ok with the adjectives? - ps: Array.isArray(o.ps) - ? o.ps - : "p" in o.ps - ? [o.ps] - : this.ps.map(o.ps) - } : {}, - ...o.desc && o.desc.reduce((all, curr) => ({ - ...all, - [curr]: true, - }), {}), - }; - }, - }; -} - -export function combineSegments(loe: (Segment | " " | "" | T.PsString)[], spaces?: "spaces"): T.PsString[] { - const first = loe[0]; - const rest = loe.slice(1); - if (!rest.length) { - if (typeof first === "string" || !("ps" in first)) { - throw new Error("can't end with a spacer"); - } - return first.ps; - } - return combineSegments(rest, spaces).flatMap(r => ( - (typeof first === "object" && "ps" in first) - ? first.ps.map(f => ( - spaces ? concatPsString(f, " ", r) : concatPsString(f, r) - )) - : [concatPsString(first, r)] - ) - ); -} - -export function flattenLengths(r: T.SingleOrLengthOpts): T.PsString[] { - if ("long" in r) { - return Object.values(r).flat(); - } - if (Array.isArray(r)) { - return r; - } - return [r]; -} - -export function putKidsInKidsSection(segments: Segment[], kids: Segment[]): Segment[] { - const first = segments[0]; - const rest = segments.slice(1); - return [ - first, - // TODO: simplify to just isKidAfterHead ?? - ...(first.isVerbHead && rest[0] && rest[0].isVerbRest) - ? kids.map(k => k.adjust({ desc: ["isKidBetweenHeadAndRest"] })) - : kids, - ...rest, - ]; -} - -export function splitOffLeapfrogWord(psVs: T.PsString[]): [T.PsString[], T.PsString[]] { - return psVs.reduce((tp, ps) => { - const pWords = ps.p.split(" "); - const fWords = ps.f.split(" "); - const beginning = makePsString( - pWords.slice(0, -1).join(" "), - fWords.slice(0, -1).join(" "), - ); - const end = makePsString( - pWords.slice(-1).join(" "), - fWords.slice(-1).join(" "), - ); - return [[...tp[0], beginning], [...tp[1], end]]; - }, [[], []] as [T.PsString[], T.PsString[]]); -} diff --git a/src/lib/phrase-building/vp-tools.ts b/src/lib/phrase-building/vp-tools.ts index 24877ef..c92b362 100644 --- a/src/lib/phrase-building/vp-tools.ts +++ b/src/lib/phrase-building/vp-tools.ts @@ -155,7 +155,7 @@ export function getTenseFromVerbSelection(vs: T.VerbSelection): T.VerbTense | T. return "perfectiveFutureModal"; } if (tn === "perfectivePast") { - return "perfectiveFutureModal"; + return "perfectivePastModal"; } if (tn === "imperfectivePast") { return "imperfectivePastModal"; diff --git a/src/lib/verb-info.ts b/src/lib/verb-info.ts index 92ad2db..9e36235 100644 --- a/src/lib/verb-info.ts +++ b/src/lib/verb-info.ts @@ -1006,14 +1006,6 @@ function getPassiveStemPerfectiveSplit(stem: T.OptionalPersonInflections>, splitInfo: T.SplitInfo): T.SplitInfo { const si = "long" in splitInfo ? splitInfo.long : splitInfo; - // if ("long" in splitInfo) { - // return { - // // @ts-ignore - // short: getPassiveRootPerfectiveSplit(root, splitInfo.long, "short"), - // // @ts-ignore - // long: getPassiveRootPerfectiveSplit(root, splitInfo.long, "long"), - // }; - // } if ("mascSing" in si) { if (!("mascSing" in root)) throw new Error("persInflections doesn't match perfective split"); return { diff --git a/src/library.ts b/src/library.ts index 144a6d2..cf73096 100644 --- a/src/library.ts +++ b/src/library.ts @@ -15,7 +15,6 @@ import { import { getVerbInfo, } from "./lib/verb-info"; -import ConjugationViewer from "./components/ConjugationViewer"; import InflectionsTable from "./components/InflectionsTable"; import Pashto from "./components/Pashto"; import Phonetics from "./components/Phonetics"; @@ -113,7 +112,7 @@ import { } from "./lib/misc-helpers"; import { flattenLengths, -} from "./lib/phrase-building/segment"; +} from "./lib/phrase-building/compile"; import { simplifyPhonetics, } from "./lib/simplify-phonetics"; @@ -235,7 +234,6 @@ export { // COMPONENTS EPExplorer, VPExplorer, - ConjugationViewer, // TODO: Deprecated - remove Examples, VerbFormDisplay, VerbTable, diff --git a/src/types.ts b/src/types.ts index b244b9f..b4e71ef 100644 --- a/src/types.ts +++ b/src/types.ts @@ -505,7 +505,6 @@ export type Pattern6FemEntry = T & { __brand3: "non anim export type NonInflecting = T & { __brand3: "non-inflecting" }; export type Entry = NounEntry | AdjectiveEntry | AdverbEntry | VerbEntry; -export type RenderedVPSBlock = (Rendered | Rendered | Rendered); // TODO: make this Rendered with recursive Rendered<> export type VPRendered = { type: "VPRendered", @@ -514,8 +513,8 @@ export type VPRendered = { isPast: boolean, isTransitive: boolean, isCompound: "stative" | "dynamic" | false, - blocks: RenderedVPSBlock[], - verb: VerbRendered, + blocks: Block[][], + kids: Kid[], englishBase?: string[], form: FormVersion, whatsAdjustable: "both" | "king" | "servant", @@ -838,7 +837,7 @@ export type EquativeRendered = EquativeSelection & { export type EPRendered = { type: "EPRendered", - blocks: Block[], + blocks: Block[][], kids: Kid[], englishBase?: string[], omitSubject: boolean, @@ -866,12 +865,54 @@ export type EntryLookupPortal = { } export type EquativeBlock = { type: "equative", equative: EquativeRendered }; +export type VerbComplementBlock = { + type: "verbComplement", + complement: PsString, +}; +export type PerfectParticipleBlock = { + type: "perfectParticipleBlock", + ps: SingleOrLengthOpts, + verb: VerbRenderedBlock, + person: Person, +}; +export type PerfectEquativeBlock = { + type: "perfectEquativeBlock", + ps: PsString[], + person: Person, +}; +export type ModalVerbBlock = { + type: "modalVerbBlock", + ps: SingleOrLengthOpts, + verb: VerbRenderedBlock, +}; +export type ModalVerbKedulPart = { + type: "modalVerbKedulPart", + ps: PsString[], + verb: VerbRenderedBlock, +}; +export type PerfectiveHeadBlock = { type: "perfectiveHead", ps: PsString }; +export type VerbRenderedBlock = { + type: "verb", + block: Omit & { + hasBa: boolean, + ps: SingleOrLengthOpts, + person: Person, + }, +}; export type Block = | Rendered + | Rendered | Rendered | Rendered - | { type: "nu" } + | PerfectParticipleBlock + | PerfectEquativeBlock + | ModalVerbBlock + | ModalVerbKedulPart + | { type: "negative", imperative: boolean } + | PerfectiveHeadBlock + | VerbRenderedBlock + | VerbComplementBlock | EquativeBlock; export type Kid =