diff --git a/package.json b/package.json index 2410a99..0358b8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lingdocs/pashto-inflector", - "version": "2.8.0", + "version": "2.8.1", "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/blocks/Block.tsx b/src/components/blocks/Block.tsx new file mode 100644 index 0000000..1b668d0 --- /dev/null +++ b/src/components/blocks/Block.tsx @@ -0,0 +1,306 @@ +import * as T from "../../types"; +import classNames from "classnames"; +import { + getEnglishFromRendered, +} from "../../lib/phrase-building/np-tools"; + +function Block({ opts, block }: { + opts: T.TextOptions, + block: T.Block, +}) { + if ("equative" in block) { + return ; + } + if (block.type === "AP") { + const english = getEnglishFromRendered(block); + return {block} + } + if (block.type === "subjectSelection") { + return + } + if (block.type === "predicateSelection") { + const english = getEnglishFromRendered(block.selection); + return
+
Pred
+ {block.selection.type === "EQComp" + ? + : {block.selection}} +
+ } + if (block.type === "nu") { + return + } + return null; +} + +export default Block; + +function NUBlock({ opts }: { + opts: T.TextOptions, +}) { + return
+
+ nu +
+
not
+
; +} + +function EquativeBlock({ opts, eq }: { + opts: T.TextOptions, + eq: T.EquativeRendered, +}) { + return
+
+ {"short" in eq.ps ? eq.ps.short[0].f : eq.ps[0].f} +
+
; +} + +function SubjectBlock({ opts, np }: { + opts: T.TextOptions, + np: T.Rendered, +}) { + const english = getEnglishFromRendered(np); + return
+
Subject
+ {np} +
; +} + +function EqCompBlock({ opts, comp }: { + opts: T.TextOptions, + comp: T.Rendered, +}) { + function AdjectiveBlock({ opts, adj }: { + opts: T.TextOptions, + adj: T.Rendered, + }) { + return
+
+ {adj.ps[0].f} +
+
Adj.
+ {adj.e &&
{adj.e}
} +
; + } + + function LocAdvBlock({ opts, adv }: { + opts: T.TextOptions, + adv: T.Rendered, + }) { + return
+
+ {adv.ps[0].f} +
+
Loc. Adv.
+ {adv.e &&
{adv.e}
} +
; + } + + return
+
Comp.
+ {comp.type === "adjective" + ? + : comp.type === "loc. adv." + ? + :
+ +
Sandwich
+
} +
; +} + +function AP({ opts, children, english }: { + opts: T.TextOptions, + children: T.Rendered, + english?: string, +}) { + const ap = children; + if (ap.selection.type === "adverb") { + return
+
+ {ap.selection.ps[0].f} +
+
AP
+ {english &&
{english}
} +
; + } + return
+ +
AP
+ {english &&
{english}
} +
; +} + +function Sandwich({ opts, sandwich }: { + opts: T.TextOptions, + sandwich: T.Rendered>, +}) { + return
+
Sandwich 🥪
+
+
+ {sandwich.inside.selection.type !== "pronoun" ? sandwich.inside.selection.possesor : undefined} +
{sandwich.before ? sandwich.before.f : ""}
+
+ {sandwich.inside} +
+
{sandwich.after ? sandwich.after.f : ""}
+
+
+
; +} + +function NP({ opts, children, inside, english }: { + opts: T.TextOptions, + children: T.Rendered, + inside?: boolean, + english?: string, +}) { + const np = children; + const hasPossesor = !!(np.selection.type !== "pronoun" && np.selection.possesor); + return
+
+ {!inside && {np.selection.type !== "pronoun" ? np.selection.possesor : undefined}} + {np.selection.adjectives} +
{np.selection.ps[0].f}
+
+
NP
+ {english &&
{english}
} +
+} + +function Possesors({ opts, children }: { + opts: T.TextOptions, + children: { shrunken: boolean, np: T.Rendered } | undefined, +}) { + if (!children) { + return null; + } + const contraction = checkForContraction(children.np); + return
+ {children.np.selection.type !== "pronoun" && {children.np.selection.possesor}} +
+ {contraction &&
({contraction})
} +
+
du
+
+ {children.np} +
+
+ +
+
+} + +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 }: { + opts: T.TextOptions, + children: T.Rendered[] | undefined, +}) { + if (!children) { + return null; + } + return + {children.map(a => a.ps[0].f).join(" ")}{` `} + +} diff --git a/src/components/ep-explorer/EPBlocksDisplay.tsx b/src/components/ep-explorer/EPBlocksDisplay.tsx new file mode 100644 index 0000000..42fab10 --- /dev/null +++ b/src/components/ep-explorer/EPBlocksDisplay.tsx @@ -0,0 +1,18 @@ +import * as T from "../../types"; +import Block from "../blocks/Block"; + +function EPBlocksDisplay({ opts, rendered }: { opts: T.TextOptions, rendered: T.EPRendered }) { + const blocks = rendered.omitSubject + ? rendered.blocks.filter(b => b.type !== "subjectSelection") + : rendered.blocks; + + return
+ {blocks.map((block, i) => ( +
+ +
+ ))} +
; +} + +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 a701900..c7126bf 100644 --- a/src/components/ep-explorer/EPDisplay.tsx +++ b/src/components/ep-explorer/EPDisplay.tsx @@ -1,17 +1,22 @@ import * as T from "../../types"; import { completeEPSelection, renderEP } from "../../lib/phrase-building/render-ep"; import { compileEP } from "../../lib/phrase-building/compile"; -import Examples from "../Examples"; import ButtonSelect from "../ButtonSelect"; -import { getRenderedSubjectSelection, getSubjectSelection } from "../../lib/phrase-building/blocks-utils"; +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"; function EPDisplay({ eps, opts, setOmitSubject }: { eps: T.EPSelectionState, opts: T.TextOptions, setOmitSubject: (value: "true" | "false") => void, }) { + const [mode] = useState("text"); const EP = completeEPSelection(eps); const subject = getSubjectSelection(eps.blocks); + if (!EP) { return
{(!subject && !eps.predicate[eps.predicate.type]) @@ -25,9 +30,11 @@ function EPDisplay({ eps, opts, setOmitSubject }: { } const rendered = renderEP(EP); const result = compileEP(rendered); - const renderedSubject = getRenderedSubjectSelection(rendered.blocks).selection; + const renderedSubject = getSubjectSelectionFromBlocks(rendered.blocks).selection; + const renderedPredicate = getPredicateSelectionFromBlocks(rendered.blocks).selection; return
-
+
+ {/* */} +
- {"long" in result.ps ? -
- - - {result.ps.mini && } -
- : - } - {result.e &&
- {result.e.map((e, i) =>
{e}
)} -
} + {mode === "text" + ? + : } {EP.predicate.selection.selection.type === "participle" &&

⚠️ NOTE: This means that the subject {renderedSubject.selection.e ? `(${renderedSubject.selection.e})` : ""} is the action/idea of {` `} - "{rendered.predicate.selection.e ? rendered.predicate.selection.e : "the particple"}".

+ "{renderedPredicate.selection.e ? renderedPredicate.selection.e : "the particple"}".

It does not mean that the subject is doing the action, which is what the transaltion sounds like in English.

}
} -function VariationLayer({ vs, opts }: { vs: T.PsString[], opts: T.TextOptions }) { - return
- {vs} -
; -} +// 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/EPExplorer.tsx b/src/components/ep-explorer/EPExplorer.tsx index 5e5c63e..dfb8379 100644 --- a/src/components/ep-explorer/EPExplorer.tsx +++ b/src/components/ep-explorer/EPExplorer.tsx @@ -38,7 +38,7 @@ function EPExplorer(props: { entryFeeder: T.EntryFeeder, }) { const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode"); - const [eps, adjustEps] = useStickyReducer(epsReducer, blankEps, "EPState5", flashMessage); + const [eps, adjustEps] = useStickyReducer(epsReducer, blankEps, "EPState6", flashMessage); const [alert, setAlert] = useState(undefined); const [showClipped, setShowClipped] = useState(""); const parent = useRef(null); diff --git a/src/components/ep-explorer/EPTextDisplay.tsx b/src/components/ep-explorer/EPTextDisplay.tsx new file mode 100644 index 0000000..ee1f2bf --- /dev/null +++ b/src/components/ep-explorer/EPTextDisplay.tsx @@ -0,0 +1,28 @@ +import * as T from "../../types"; +import Examples from "../Examples"; + +function EPTextDisplay({ compiled, opts }: { compiled: { + ps: T.SingleOrLengthOpts; + e?: string[] | undefined; +}, opts: T.TextOptions }) { + function VariationLayer({ vs }: { vs: T.PsString[] }) { + return
+ {vs} +
; + } + return
+ {"long" in compiled.ps ? +
+ + + {compiled.ps.mini && } +
+ : + } + {compiled.e &&
+ {compiled.e.map((e, i) =>
{e}
)} +
} +
; +} + +export default EPTextDisplay; \ 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 4ec35f5..9e7f891 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 { checkForMiniPronounsError } from "../../lib/phrase-building/compile"; +import { checkEPForMiniPronounsError } from "../../lib/phrase-building/compile"; import { adjustSubjectSelection, getSubjectSelection, insertNewAP, removeAP, setAP, shiftBlock } from "../../lib/phrase-building/blocks-utils"; 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 = checkForMiniPronounsError(eps); + const error = checkEPForMiniPronounsError(eps); if (error) { if (sendAlert) sendAlert(error); return old; diff --git a/src/components/vp-explorer/VPExplorerQuiz.tsx b/src/components/vp-explorer/VPExplorerQuiz.tsx index 8a82680..e8e7017 100644 --- a/src/components/vp-explorer/VPExplorerQuiz.tsx +++ b/src/components/vp-explorer/VPExplorerQuiz.tsx @@ -106,6 +106,12 @@ function VPExplorerQuiz(props: { setAnswerBlank(""); setQuizState(tickQuizState(quizState.vps)); } + if (1 < 3) { + return
+

The quiz is broken 😭

+

It will be fixed in like a week or two... hopefully.

+
+ } return
@@ -322,6 +328,7 @@ function tickQuizState(startingWith: T.VPSelectionComplete | QuizState): QuizSta } const answer = makeRes(newVps); const wrongAnswers = wrongVpsS.map(makeRes); + console.log({ newVps, answer, wrongAnswers }); const allAnswers = shuffleArray([...wrongAnswers, answer]); const options = allAnswers.map(getOptionFromResult); return { diff --git a/src/lib/accent-helpers.ts b/src/lib/accent-helpers.ts index 59719ea..47961f6 100644 --- a/src/lib/accent-helpers.ts +++ b/src/lib/accent-helpers.ts @@ -108,6 +108,20 @@ function accentSyllable(s: string): string { }); } +export function removeAccentsWLength(s: T.SingleOrLengthOpts): T.SingleOrLengthOpts { + if ("long" in s) { + return { + long: removeAccentsWLength(s.long) as T.PsString[], + short: removeAccentsWLength(s.short) as T.PsString[], + ...s.mini ? { + mini: removeAccentsWLength(s.mini) as T.PsString[], + } : {}, + }; + } + return removeAccents(s); +} + + export function removeAccents(s: T.PsString): T.PsString; export function removeAccents(s: string): string; export function removeAccents(s: T.PsString[]): T.PsString[]; diff --git a/src/lib/misc-helpers.ts b/src/lib/misc-helpers.ts index 24aca22..8ddc093 100644 --- a/src/lib/misc-helpers.ts +++ b/src/lib/misc-helpers.ts @@ -28,6 +28,12 @@ export function pickPersInf(s: T.OptionalPersonInflections, persInf: T.Per return s; } +export function getFirstSecThird(p: T.Person): 1 | 2 | 3 { + if ([0, 1, 6, 7].includes(p)) return 1; + if ([2, 3, 8, 9].includes(p)) return 2; + return 3; +} + // export function pickPersInf( // s: T.OptionalPersonInflections>, // persInf: T.PersonInflectionsField, diff --git a/src/lib/p-text-helpers.ts b/src/lib/p-text-helpers.ts index 51b5b5b..b392cac 100644 --- a/src/lib/p-text-helpers.ts +++ b/src/lib/p-text-helpers.ts @@ -984,6 +984,10 @@ 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 getLong(x: T.SingleOrLengthOpts): U { if ("long" in x) { return x.long; @@ -991,6 +995,13 @@ export function getLong(x: T.SingleOrLengthOpts): U { return x; } +export function getShort(a: T.SingleOrLengthOpts): U { + if ("long" in a) { + return a.short; + } + return a; +} + export function capitalizeFirstLetter(string: string) { return string.charAt(0).toUpperCase() + string.slice(1); } \ No newline at end of file diff --git a/src/lib/phrase-building/blocks-utils.ts b/src/lib/phrase-building/blocks-utils.ts index e26fb56..44ec07d 100644 --- a/src/lib/phrase-building/blocks-utils.ts +++ b/src/lib/phrase-building/blocks-utils.ts @@ -1,4 +1,5 @@ import * as T from "../../types"; +import { getLength } from "../p-text-helpers"; export function getSubjectSelection(blocks: T.EPSBlockComplete[] | T.VPSBlockComplete[]): T.SubjectSelectionComplete; export function getSubjectSelection(blocks: T.EPSBlock[] | T.VPSBlock[]): T.SubjectSelection; @@ -10,6 +11,26 @@ 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"); + 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"); + 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 getObjectSelection(blocks: T.VPSBlockComplete[]): T.ObjectSelectionComplete; export function getObjectSelection(blocks: T.VPSBlock[]): T.ObjectSelection; export function getObjectSelection(blocks: T.VPSBlock[] | T.VPSBlockComplete[]): T.ObjectSelection | T.ObjectSelectionComplete { @@ -185,6 +206,29 @@ 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 hasEquativeWithLengths(blocks: T.Block[]): boolean { + const equative = blocks.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; +} + 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 d434e34..433e793 100644 --- a/src/lib/phrase-building/compile.ts +++ b/src/lib/phrase-building/compile.ts @@ -1,6 +1,6 @@ import * as T from "../../types"; import { - concatPsString, + concatPsString, getLong, } from "../p-text-helpers"; import { Segment, @@ -8,7 +8,7 @@ import { flattenLengths, combineSegments, splitOffLeapfrogWord, - putKidsInKidsSection, + putKidsInKidsSection as oldPutKidsInKidsSection, } from "./segment"; import { removeAccents, @@ -25,15 +25,17 @@ import { pronouns } from "../grammar-units"; import { completeEPSelection, renderEP } from "./render-ep"; import { completeVPSelection } from "./vp-tools"; import { renderVP } from "./render-vp"; -import { getRenderedObjectSelection, getRenderedSubjectSelection } from "./blocks-utils"; +import { getAPsFromBlocks, getPredicateSelectionFromBlocks, getRenderedObjectSelection, getRenderedSubjectSelection, getSubjectSelectionFromBlocks, hasEquativeWithLengths, specifyEquativeLength } from "./blocks-utils"; -const blank: T.PsString = { - p: "______", - f: "______", -}; +// TODO: GET BLANKING WORKING! + +// const blank: T.PsString = { +// p: "______", +// f: "______", +// }; type BlankoutOptions = { equative?: boolean, ba?: boolean, kidsSection?: boolean }; -const kidsBlank = makeSegment({ p: "___", f: "___" }, ["isKid"]); +// const kidsBlank = makeSegment({ p: "___", f: "___" }, ["isKid"]); 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 [] }; @@ -80,7 +82,7 @@ function compilePs({ blocks, kids, verb: { head, rest }, VP }: CompilePsInput): 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 = putKidsInKidsSection([...blocks, ...verbSegments], kids); + 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 @@ -145,37 +147,6 @@ export function getVPSegmentsAndKids(VP: T.VPRendered, form?: T.FormVersion): { }; } -export function getEPSegmentsAndKids(EP: T.EPRendered, blankOut?: BlankoutOptions): { kids: Segment[], blocks: Segment[] } { - const possToShrink = findPossesivesToShrinkInEP(EP); - const blocks = EP.blocks.reduce((accum, block) => { - if (block.type === "subjectSelection") { - if (EP.omitSubject) return accum; - return [ - ...accum, - makeSegment(getPashtoFromRendered(block.selection, false)), - ]; - } - return [ - ...accum, - makeSegment(getPashtoFromRendered(block, false)), - ]; - }, [] as Segment[]); - const predicate = makeSegment(getPashtoFromRendered(EP.predicate, false)); - return { - kids: blankOut?.kidsSection ? [kidsBlank] : orderKidsSection([ - ...EP.equative.hasBa - ? ( - blankOut?.ba ? [kidsBlank] : [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])]) - : [], - ...possToShrink.map(a => shrinkNP(a.np)), - ]), - blocks: [ - ...blocks, - predicate - ], - }; -} - function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[], V: T.VerbRendered): Segment[][] { const hasLeapfrog = isPerfectTense(V.tense) || isModalTense(V.tense); const rest = (() => { @@ -292,42 +263,67 @@ function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[ 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[] }; export function compileEP(EP: T.EPRendered, combineLengths?: boolean, blankOut?: BlankoutOptions): { ps: T.SingleOrLengthOpts, e?: string[] } { - const { kids, blocks } = getEPSegmentsAndKids(EP, blankOut); - const equative = EP.equative.ps; - const psResult = compileEPPs({ - blocks, - kids, - equative: blankOut?.equative ? [blank] : equative, - negative: EP.equative.negative, - }); + const psResult = compileEPPs(EP.blocks, EP.kids, blankOut); return { ps: combineLengths ? flattenLengths(psResult) : psResult, e: compileEnglishEP(EP), }; } -function compileEPPs({ blocks, kids, equative, negative }: { - blocks: Segment[], - kids: Segment[], - equative: T.SingleOrLengthOpts, - negative: boolean, -}): T.SingleOrLengthOpts { - if ("long" in equative) { +function compileEPPs(blocks: T.Block[], kids: T.Kid[], blankOut?: BlankoutOptions): T.SingleOrLengthOpts { + if (hasEquativeWithLengths(blocks)) { return { - long: compileEPPs({ blocks, kids, equative: equative.long, negative }) as T.PsString[], - short: compileEPPs({ blocks, kids, equative: equative.short, negative }) as T.PsString[], + long: compileEPPs(specifyEquativeLength(blocks, "long"), kids, blankOut) as T.PsString[], + short: compileEPPs(specifyEquativeLength(blocks, "short"), kids, blankOut) as T.PsString[], }; } - const allSegments = putKidsInKidsSection([ - ...blocks, - ...negative ? [ - makeSegment({ p: "نه", f: "nú" }), - makeSegment(removeAccents(equative)) - ] : [ - makeSegment(equative), - ], - ], kids); - return removeDuplicates(combineSegments(allSegments, "spaces")); + const subjectPerson = getSubjectSelectionFromBlocks(blocks) + .selection.selection.person; + const blocksWKids = putKidsInKidsSection(blocks, kids); + return removeDuplicates(combineIntoText(blocksWKids, subjectPerson)); +} + +function combineIntoText(pieces: (T.Block | T.Kid)[], subjectPerson: T.Person): T.PsString[] { + const first = pieces[0]; + const rest = pieces.slice(1); + const firstPs = getPsFromPiece(first, subjectPerson); + if (!rest.length) { + return firstPs; + } + return combineIntoText(rest, subjectPerson).flatMap(r => ( + firstPs.map(fPs => concatPsString(fPs, " ", r)) + ) + ); +} + +function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsString[] { + if (piece.type === "ba") { + return [grammarUnits.baParticle]; + } + if (piece.type === "mini-pronoun") { + return [piece.ps]; + } + if (piece.type === "nu") { + return [{ p: "نه", f: "nú" }]; + } + if (piece.type === "equative") { + // length will already be specified in compileEPPs - this is just for type safety + return getLong(piece.equative.ps); + } + if (piece.type === "subjectSelection" || piece.type === "predicateSelection") { + return getPashtoFromRendered(piece.selection, subjectPerson); + } + // if (piece.type === "AP") { + return getPashtoFromRendered(piece, subjectPerson); + // } +} + +function getEngAPs(blocks: T.Block[]): string { + return getAPsFromBlocks(blocks).reduce((accum, curr) => { + const e = getEnglishFromRendered(curr); + if (!e) return accum; + return `${accum} ${e}`; + }, ""); } function getEnglishAPs(blocks: (T.Rendered | T.Rendered | T.RenderedVPSBlock)[]) { @@ -339,6 +335,16 @@ function getEnglishAPs(blocks: (T.Rendered | T.Rende }, ""); } +function putKidsInKidsSection(blocks: T.Block[], kids: T.Kid[]): (T.Block | T.Kid)[] { + const first = blocks[0]; + const rest = blocks.slice(1); + return [ + first, + ...kids, + ...rest, + ]; +} + function mergeSegments(s1: Segment, s2: Segment, noSpace?: "no space"): Segment { if (noSpace) { @@ -405,10 +411,11 @@ function compileEnglishEP(EP: T.EPRendered): string[] | undefined { function insertEWords(e: string, { subject, predicate, APs }: { subject: string, predicate: string, APs: string }): string { return e.replace("$SUBJ", subject).replace("$PRED", predicate || "") + APs; } - const engSubjR = getRenderedSubjectSelection(EP.blocks).selection; + const engSubjR = getSubjectSelectionFromBlocks(EP.blocks).selection; + const engPredR = getPredicateSelectionFromBlocks(EP.blocks).selection; const engSubj = getEnglishFromRendered(engSubjR); - const engPred = getEnglishFromRendered(EP.predicate); - const engAPs = getEnglishAPs(EP.blocks); + const engPred = getEnglishFromRendered(engPredR); + const engAPs = getEngAPs(EP.blocks); // require all English parts for making the English phrase const b = (EP.englishBase && engSubj && engPred) ? EP.englishBase.map(e => insertEWords(e, { @@ -440,7 +447,33 @@ export function orderKidsSection(kids: Segment[]): Segment[] { }); } -export function checkForMiniPronounsError(s: T.EPSelectionState | T.VPSelectionState): undefined | string { +export function checkEPForMiniPronounsError(s: T.EPSelectionState): 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 + )); + if (duplicates.length === 0) return undefined; + return duplicates[0]; + } + const kids = (() => { + const EPS = completeEPSelection(s); + if (!EPS) return undefined; + const { kids } = renderEP(EPS); + return kids; + })(); + if (!kids) return undefined; + const miniPronouns = kids.filter(x => x.type === "mini-pronoun") as T.MiniPronoun[]; + if (miniPronouns.length > 2) { + return "can't add another mini-pronoun, there are alread two"; + } + const duplicateMiniPronoun = findDuplicateMiniP(miniPronouns); + if (duplicateMiniPronoun) { + return `there's already a ${duplicateMiniPronoun.ps.p} - ${duplicateMiniPronoun.ps.f} mini-pronoun in use, can't have two of those`; + } + 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 @@ -449,12 +482,6 @@ export function checkForMiniPronounsError(s: T.EPSelectionState | T.VPSelectionS return duplicates[0]; } const kids = (() => { - if ("predicate" in s) { - const EPS = completeEPSelection(s); - if (!EPS) return undefined; - const { kids } = getEPSegmentsAndKids(renderEP(EPS)); - return kids; - } const VPS = completeVPSelection(s); if (!VPS) return undefined; const { kids } = getVPSegmentsAndKids(renderVP(VPS)); @@ -536,44 +563,6 @@ function findPossesivesInAdjective(a: T.Rendered): T.Rende return findPossesivesInNP(a.sandwich.inside); } -type FoundNP = { - np: T.Rendered, - from: "subject" | "predicate" | "AP", -}; - -export function findPossesivesToShrinkInEP(EP: T.EPRendered): FoundNP[] { - const inBlocks: FoundNP[] = EP.blocks.reduce((accum, block): FoundNP[] => { - if (block.type === "subjectSelection") { - if (EP.omitSubject) return accum; - const found: FoundNP[] = findPossesivesInNP(block.selection).map(np => ({ np, from: "subject" })); - return [ - ...accum, - ...found, - ]; - } - if (block.selection.type === "sandwich") { - const found: FoundNP[] = findPossesivesInNP(block.selection.inside).map(np => ({ np, from: "AP" })); - return [ - ...accum, - ...found, - ]; - } - return accum; - }, [] as FoundNP[]); - const inPredicate: FoundNP[] = ((EP.predicate.selection.type === "loc. adv.") - ? [] - : (EP.predicate.selection.type === "adjective") - ? findPossesivesInAdjective(EP.predicate.selection) - : EP.predicate.selection.type === "sandwich" - ? findPossesivesInNP(EP.predicate.selection.inside) - : EP.predicate.selection.type === "pronoun" - ? [] - : findPossesivesInNP({ type: "NP", selection: EP.predicate.selection })).map(np => ({ np, from: "predicate" })); - return [ - ...inBlocks, - ...inPredicate, - ]; -} // export function findPossesiveToShrinkInVP(VP: T.VPRendered): T.Rendered[] { // const obj: T.Rendered | undefined = ("object" in VP && typeof VP.object === "object") diff --git a/src/lib/phrase-building/render-ep.ts b/src/lib/phrase-building/render-ep.ts index b78e768..4faa18e 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 { getPersonFromVerbForm } from "../../lib/misc-helpers"; +import { getFirstSecThird, getPersonFromVerbForm } from "../../lib/misc-helpers"; import { getVerbBlockPosFromPerson } from "../misc-helpers"; import { getEnglishWord } from "../get-english-word"; import { psStringFromEntry } from "../p-text-helpers"; @@ -12,39 +12,143 @@ import { isLocativeAdverbEntry } from "../type-predicates"; 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"; export function renderEP(EP: T.EPSelectionComplete): T.EPRendered { - const subject = getSubjectSelection(EP.blocks).selection; - const king = (subject.selection.type === "pronoun") - ? "subject" - : EP.predicate.type === "NP" - ? "predicate" - : "subject"; - // TODO: less repetative logic - const kingPerson = king === "subject" - ? getPersonFromNP(subject) - : EP.predicate.type === "NP" - ? getPersonFromNP(EP.predicate.selection) - : getPersonFromNP(subject); - const kingIsParticiple = king === "subject" - ? (subject.selection.type === "participle") - : (EP.predicate.type === "NP" && EP.predicate.selection.selection.type === "participle"); + const { kids, blocks, englishEquativePerson } = getEPSBlocksAndKids(EP); return { type: "EPRendered", - king: EP.predicate.type === "Complement" ? "subject" : "predicate", - blocks: renderEPSBlocks(EP.blocks, king), - predicate: EP.predicate.type === "NP" - ? renderNPSelection(EP.predicate.selection, false, true, "subject", "king") - : renderEqCompSelection(EP.predicate.selection, kingPerson), - equative: renderEquative(EP.equative, kingPerson), + blocks, + kids, englishBase: equativeBuilders[EP.equative.tense]( - kingIsParticiple ? T.Person.ThirdSingMale : kingPerson, + englishEquativePerson, EP.equative.negative, ), omitSubject: EP.omitSubject, }; } +function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks: T.Block[], englishEquativePerson: T.Person } { + const subject = getSubjectSelection(EP.blocks).selection; + const commandingNP: T.NPSelection = subject.selection.type === "pronoun" + ? subject + : EP.predicate.selection.type === "NP" + ? EP.predicate.selection + : subject; + const commandingPerson = getPersonFromNP(commandingNP); + const equative: T.EquativeBlock = { type: "equative", equative: renderEquative(EP.equative, commandingPerson) }; + const blocks: T.Block[] = [ + ...renderEPSBlocks(EP.omitSubject ? EP.blocks.filter(b => b.block.type !== "subjectSelection") : EP.blocks), + { + type: "predicateSelection", + selection: EP.predicate.selection.type === "NP" + ? 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); + const kids: T.Kid[] = orderKids([ + ...equative.equative.hasBa ? [{ type: "ba" } as T.Kid] : [], + ...miniPronouns, + ]); + return { + blocks, + kids, + englishEquativePerson: commandingNP.selection.type === "participle" ? T.Person.ThirdSingMale : commandingPerson, + }; +} + +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 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); +} + export function getEquativeForm(tense: T.EquativeTense): { hasBa: boolean, form: T.SingleOrLengthOpts } { const hasBa = (tense === "future" || tense === "wouldBe"); const baseTense = (tense === "future") @@ -58,7 +162,7 @@ export function getEquativeForm(tense: T.EquativeTense): { hasBa: boolean, form: } } -function renderEPSBlocks(blocks: T.EPSBlockComplete[], king: "subject" | "predicate"): (T.Rendered | T.Rendered)[] { +function renderEPSBlocks(blocks: T.EPSBlockComplete[]): T.Block[] { return blocks.map(({ block }): (T.Rendered | T.Rendered) => { if (block.type === "AP") { if (block.selection.type === "adverb") { @@ -76,7 +180,7 @@ function renderEPSBlocks(blocks: T.EPSBlockComplete[], king: "subject" | "predic } return { type: "subjectSelection", - selection: renderNPSelection(block.selection, false, false, "subject", king === "subject" ? "king" : "none") + selection: renderNPSelection(block.selection, false, false, "subject", "none") }; }); } @@ -218,7 +322,7 @@ export function completeEPSelection(eps: T.EPSelectionState): T.EPSelectionCompl ...eps, blocks: eps.blocks, predicate: { - type: "Complement", + type: "predicateSelection", selection, }, }; @@ -230,8 +334,23 @@ export function completeEPSelection(eps: T.EPSelectionState): T.EPSelectionCompl ...eps, blocks: eps.blocks, predicate: { - type: "NP", + type: "predicateSelection", selection, }, }; } + +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), + }, + }; +} \ No newline at end of file diff --git a/src/lib/phrase-building/segment.ts b/src/lib/phrase-building/segment.ts index 5833f0b..ecc248c 100644 --- a/src/lib/phrase-building/segment.ts +++ b/src/lib/phrase-building/segment.ts @@ -5,8 +5,6 @@ import { import { concatPsString, } from "../p-text-helpers"; -// SEGMENT TOOLS -// TODO: PULL OUT FOR SHARING ACROSS COMPILE EP ETC? type SegmentDescriptions = { isVerbHead?: boolean, diff --git a/src/types.ts b/src/types.ts index 363321b..b244b9f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -552,6 +552,11 @@ export type SubjectSelectionComplete = { selection: NPSelection, }; +export type PredicateSelectionComplete = { + type: "predicateSelection", + selection: EqCompSelection | NPSelection, +}; + export type ObjectSelectionComplete = { type: "objectSelection", selection: NPSelection | ObjectNP, @@ -695,6 +700,7 @@ export type Rendered< | EqCompSelection["selection"] | SubjectSelectionComplete | ObjectSelectionComplete + | PredicateSelectionComplete | AdjectiveSelection | SandwichSelection > = @@ -744,7 +750,11 @@ export type Rendered< type: "objectSelection", selection: Rendered | Person.ThirdPlurMale | "none", } - : ReplaceKey< + : T extends PredicateSelectionComplete + ? { + type: "predicateSelection", + selection: Rendered | Rendered, + } : ReplaceKey< Omit, "e", string @@ -794,13 +804,7 @@ export type VPSBlockComplete = { export type EPSelectionComplete = Omit & { blocks: EPSBlockComplete[], - predicate: { - type: "NP", - selection: NPSelection, - } | { - type: "Complement", - selection: EqCompSelection, - }, + predicate: PredicateSelectionComplete, omitSubject: boolean, }; @@ -834,10 +838,8 @@ export type EquativeRendered = EquativeSelection & { export type EPRendered = { type: "EPRendered", - king: "subject" | "predicate", - blocks: (Rendered | Rendered)[], - predicate: Rendered | Rendered, - equative: EquativeRendered, + blocks: Block[], + kids: Kid[], englishBase?: string[], omitSubject: boolean, } @@ -863,3 +865,24 @@ export type EntryLookupPortal = { getByTs: (ts: number) => (X | undefined), } +export type EquativeBlock = { type: "equative", equative: EquativeRendered }; + +export type Block = + | Rendered + | Rendered + | Rendered + | { type: "nu" } + | EquativeBlock; + +export type Kid = + | { type: "ba" } + | MiniPronoun; + +export type MiniPronoun = { + type: "mini-pronoun", + person: Person, + ps: PsString, + source: "servant" | "possesive", + np: NPSelection, +}; +