diff --git a/package.json b/package.json index 58de8ea..97d1ec3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lingdocs/pashto-inflector", - "version": "3.3.1", + "version": "3.3.2", "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/eq-comp-picker/EqCompPicker.tsx b/src/components/ComplementPicker.tsx similarity index 68% rename from src/components/ep-explorer/eq-comp-picker/EqCompPicker.tsx rename to src/components/ComplementPicker.tsx index f12854a..920855c 100644 --- a/src/components/ep-explorer/eq-comp-picker/EqCompPicker.tsx +++ b/src/components/ComplementPicker.tsx @@ -1,32 +1,37 @@ import { useState, useEffect } from "react"; -import * as T from "../../../types"; -import AdjectivePicker from "../../np-picker/AdjectivePicker"; -import LocativeAdverbPicker from "./LocativeAdverbPicker"; -import SandwichPicker from "../../np-picker/SandwichPicker"; -const compTypes: T.EqCompType[] = ["adjective", "loc. adv.", "sandwich"]; +import * as T from "../types"; +import AdjectivePicker from "./np-picker/AdjectivePicker"; +import LocativeAdverbPicker from "./ep-explorer/eq-comp-picker/LocativeAdverbPicker"; +import SandwichPicker from "./np-picker/SandwichPicker"; +const compTypes: T.ComplementType[] = ["adjective", "loc. adv.", "sandwich", "comp. noun"]; -function EqCompPicker(props: { +function selectionTypeToCompType(s: Exclude | "noun"): T.ComplementType { + if (s === "noun") return "comp. noun"; + return s; +} + +function ComplementPicker(props: { phraseIsComplete: boolean, - onChange: (comp: T.EqCompSelection | undefined) => void, - comp: T.EqCompSelection | undefined, + onChange: (comp: T.ComplementSelection | undefined) => void, + comp: T.ComplementSelection | undefined, opts: T.TextOptions, cantClear?: boolean, heading?: JSX.Element | string, entryFeeder: T.EntryFeeder, }) { - const [compType, setCompType] = useState(props.comp - ? props.comp.selection.type + const [compType, setCompType] = useState(props.comp + ? selectionTypeToCompType(props.comp.selection.type) : undefined); useEffect(() => { setCompType(props.comp - ? props.comp.selection.type + ? selectionTypeToCompType(props.comp.selection.type) : undefined); }, [props.comp]); function handleClear() { setCompType(undefined); props.onChange(undefined); } - function handleCompTypeChange(ctp: T.EqCompType) { + function handleCompTypeChange(ctp: T.ComplementType) { props.onChange(undefined); setCompType(ctp); } @@ -70,7 +75,7 @@ function EqCompPicker(props: { entryFeeder={props.entryFeeder} adjective={props.comp?.selection.type === "adjective" ? props.comp.selection : undefined} opts={props.opts} - onChange={(a) => props.onChange(a ? { type: "EQComp", selection: a } : undefined)} + onChange={(a) => props.onChange(a ? { type: "complement", selection: a } : undefined)} phraseIsComplete={props.phraseIsComplete} /> : compType === "loc. adv." @@ -78,11 +83,11 @@ function EqCompPicker(props: { entryFeeder={props.entryFeeder.locativeAdverbs} adjective={props.comp?.selection.type === "loc. adv." ? props.comp.selection : undefined} opts={props.opts} - onChange={(a) => props.onChange(a ? { type: "EQComp", selection: a } : undefined)} + onChange={(a) => props.onChange(a ? { type: "complement", selection: a } : undefined)} /> : compType === "sandwich" ? props.onChange(a ? { type: "EQComp", selection: a } : undefined)} + onChange={(a) => props.onChange(a ? { type: "complement", selection: a } : undefined)} opts={props.opts} sandwich={props.comp?.selection.type === "sandwich" ? props.comp.selection : undefined} entryFeeder={props.entryFeeder} @@ -90,9 +95,13 @@ function EqCompPicker(props: { // TODO: get phraseIsComplete working here phraseIsComplete={props.phraseIsComplete} /> + : compType === "comp. noun" + ?
+ Sorry, can't choose complement nouns yet 🚧 +
: null} ; } -export default EqCompPicker; \ No newline at end of file +export default ComplementPicker; \ No newline at end of file diff --git a/src/components/blocks/Block.tsx b/src/components/blocks/Block.tsx index 5e67222..26ee870 100644 --- a/src/components/blocks/Block.tsx +++ b/src/components/blocks/Block.tsx @@ -30,8 +30,8 @@ function Block({ opts, block, king, script }: { const english = getEnglishFromRendered(block.block.selection); return
Predicate
- {block.block.selection.type === "EQComp" - ? + {block.block.selection.type === "complement" + ? : {block.block.selection}}
} @@ -63,6 +63,9 @@ function Block({ opts, block, king, script }: { if (block.block.type === "modalVerbKedulPart") { return } + if (block.block.type === "complement") { + return ; + } return null; } @@ -99,7 +102,12 @@ function VerbSBlock({ opts, v, script }: { return
{"long" in v.ps &&
{length}
} + <> + {v.type === "verb" && v.complement && + + } {getLength(v.ps, length)[0][script]} +
{v.type === "perfectParticipleBlock" ? "Past Partic." : "Verb"}
{((v.type === "perfectParticipleBlock" ? "Past Partic." : "Verb") @@ -244,10 +252,11 @@ function ObjectBlock({ opts, obj, role, script }: {
; } -function EqCompBlock({ opts, comp, script }: { +function ComplementBlock({ opts, comp, script, inside }: { script: "p" | "f", opts: T.TextOptions, - comp: T.Rendered, + comp: T.Rendered | T.Rendered["selection"], + inside?: boolean, }) { function AdjectiveBlock({ opts, adj }: { opts: T.TextOptions, @@ -274,18 +283,31 @@ function EqCompBlock({ opts, comp, script }: { {adv.e} ; } - return
Comp.
{comp.type === "adjective" ? : comp.type === "loc. adv." - ? - :
- -
Sandwich
+ ? + : comp.type === "noun" + ? + NOT DONE YET + + : comp.type === "unselected" + ?
+ + ____ + + {!inside && <> +
 
{comp.e} -
} + } +
+ :
+ +
Sandwich
+ {comp.e} +
}
; } diff --git a/src/components/ep-explorer/EPPicker.tsx b/src/components/ep-explorer/EPPicker.tsx index 15ee4f8..d6e682c 100644 --- a/src/components/ep-explorer/EPPicker.tsx +++ b/src/components/ep-explorer/EPPicker.tsx @@ -2,7 +2,7 @@ import * as T from "../../types"; import NPPicker from "../np-picker/NPPicker"; import EquativePicker from "./EquativePicker"; import ButtonSelect from "../ButtonSelect"; -import EqCompPicker from "./eq-comp-picker/EqCompPicker"; +import ComplementPicker from "../ComplementPicker"; import epsReducer, { EpsReducerAction } from "./eps-reducer"; import { useEffect, useRef, useState } from "react"; import { completeEPSelection } from "../../lib/phrase-building/render-ep"; @@ -92,10 +92,10 @@ function EPPicker({ opts, eps, onChange, entryFeeder }: { role="subject" onChange={payload => adjustEps({ type: "set predicate NP", payload })} opts={opts} - /> : : adjustEps({ type: "set predicate comp", payload })} + onChange={payload => adjustEps({ type: "set predicate complement", payload })} opts={opts} entryFeeder={entryFeeder} />} diff --git a/src/components/ep-explorer/eps-reducer.ts b/src/components/ep-explorer/eps-reducer.ts index eeca13d..3b75442 100644 --- a/src/components/ep-explorer/eps-reducer.ts +++ b/src/components/ep-explorer/eps-reducer.ts @@ -17,8 +17,8 @@ export type EpsReducerAction = { type: "set predicate NP", payload: T.NPSelection | undefined, } | { - type: "set predicate comp", - payload: T.EqCompSelection | undefined, + type: "set predicate complement", + payload: T.ComplementSelection | undefined, } | { type: "set omitSubject", payload: "true" | "false", @@ -134,7 +134,7 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc }; return selection ? ensureMiniPronounsOk(eps, n, sendAlert) : n; } - if (action.type === "set predicate comp") { + if (action.type === "set predicate complement") { return { ...eps, predicate: { diff --git a/src/components/vp-explorer/VPExplorerQuiz.tsx b/src/components/vp-explorer/VPExplorerQuiz.tsx index ae9c681..ad00781 100644 --- a/src/components/vp-explorer/VPExplorerQuiz.tsx +++ b/src/components/vp-explorer/VPExplorerQuiz.tsx @@ -486,6 +486,7 @@ function getRandomVPSelection(mix: MixType = "both") { )), verb: randomizeTense(verb, true), form: { removeKing: false, shrinkServant: false }, + externalComplement: undefined, } } return { @@ -501,6 +502,7 @@ function getRandomVPSelection(mix: MixType = "both") { )), verb: randomizeTense(verb, true), form: { removeKing: false, shrinkServant: false }, + externalComplement: undefined, }; }; }; diff --git a/src/components/vp-explorer/VPPicker.tsx b/src/components/vp-explorer/VPPicker.tsx index 329590e..1049f36 100644 --- a/src/components/vp-explorer/VPPicker.tsx +++ b/src/components/vp-explorer/VPPicker.tsx @@ -8,7 +8,13 @@ import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationMod import { vpsReducer, VpsReducerAction } from "./vps-reducer"; import APPicker from "../ap-picker/APPicker"; import autoAnimate from "@formkit/auto-animate"; -import { getObjectSelection, getSubjectSelection, includesShrunkenServant, isNoObject } from "../../lib/phrase-building/blocks-utils"; +import { + getObjectSelection, + getSubjectSelection, + includesShrunkenServant, + isNoObject, +} from "../../lib/phrase-building/blocks-utils"; +import ComplementPicker from "../ComplementPicker"; function VPPicker({ opts, vps, onChange, entryFeeder }: { opts: T.TextOptions, @@ -156,11 +162,6 @@ function VPPicker({ opts, vps, onChange, entryFeeder }: { {!servantIsShrunk ? "🪄" : "👶"} } - {(rendered && rendered.whatsAdjustable !== "servant") && - adjustVps({ type: "toggle king remove" })} className="mx-2 clickable"> - {!VPS?.form.removeKing ? "🚫" : "🙈"} - - } } entryFeeder={entryFeeder} role="object" @@ -175,6 +176,19 @@ function VPPicker({ opts, vps, onChange, entryFeeder }: { : null} ; })} + {vps.externalComplement &&
+
Complement
+ adjustVps({ type: "set externalComplement", payload })} + opts={opts} + entryFeeder={entryFeeder} + /> +
}
void): T.VPSelectionState { return ensureMiniPronounsOk(vps, doReduce()); @@ -291,6 +294,18 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se blocks: shiftBlock(vps.blocks, index, direction), }; } + if (action.type === "set externalComplement") { + const selection = action.payload; + return { + ...vps, + externalComplement: selection === undefined + // TODO: this is a bit messy + // when using the ComplementPicker with an EP - undefined means it hasn't been selected + // when using the ComplementPicker with a VP - undefined means there can be no complement + ? { type: "complement", selection: { type: "unselected" }} + : selection, + } + } throw new Error("unknown vpsReducer state"); } } diff --git a/src/lib/phrase-building/blocks-utils.ts b/src/lib/phrase-building/blocks-utils.ts index 1b91d93..77333e8 100644 --- a/src/lib/phrase-building/blocks-utils.ts +++ b/src/lib/phrase-building/blocks-utils.ts @@ -99,7 +99,18 @@ export function getPredicateSelectionFromBlocks(blocks: T.Block[][]): T.Rendered export function getAPsFromBlocks(blocks: T.Block[][]): T.Rendered[] { return blocks[0].filter(b => b.block.type === "AP").map(b => b.block) as T.Rendered[]; -} +} + +export function getComplementFromBlocks(blocks: T.Block[][]): T.Rendered | T.Rendered | undefined { + const complement = blocks[0].find(b => b.block.type === "complement"); + if (!complement) { + return undefined; + } + if (complement.block.type !== "complement") { + throw new Error("error finding complement - other kind of block retrieved"); + } + return complement.block; +} export function getObjectSelection(blocks: T.VPSBlockComplete[]): T.ObjectSelectionComplete; export function getObjectSelection(blocks: T.VPSBlock[]): T.ObjectSelection; @@ -256,7 +267,11 @@ export function removeAP(blocks: B, index } export function isNoObject(b: T.VPSBlock["block"] | T.EPSBlock["block"]): b is { type: "objectSelection", selection: "none" } { - return !!(b && b.type === "objectSelection" && b.selection === "none"); + return !!( + b + && + (b.type === "objectSelection" && b.selection === "none") + ); } export function specifyEquativeLength(blocksWVars: T.Block[][], length: "long" | "short"): T.Block[][] { diff --git a/src/lib/phrase-building/compile.ts b/src/lib/phrase-building/compile.ts index 54e01d8..9ba4006 100644 --- a/src/lib/phrase-building/compile.ts +++ b/src/lib/phrase-building/compile.ts @@ -14,12 +14,12 @@ import { completeVPSelection } from "./vp-tools"; import { renderVP } from "./render-vp"; import { getAPsFromBlocks, + getComplementFromBlocks, getObjectSelectionFromBlocks, getPredicateSelectionFromBlocks, getSubjectSelectionFromBlocks, getVerbFromBlocks, hasEquativeWithLengths, - hasVerbWithLengths, specifyEquativeLength, specifyVerbLength, } from "./blocks-utils"; @@ -81,10 +81,14 @@ export function compileVP(VP: T.VPRendered, form: T.FormVersion, combineLengths? } function compileVPPs(blocks: T.Block[][], kids: T.Kid[], form: T.FormVersion, king: "subject" | "object"): T.SingleOrLengthOpts { - if (hasVerbWithLengths(blocks)) { + const verbBlock = getVerbFromBlocks(blocks); + if ("long" in verbBlock.block.ps) { return { long: compileVPPs(specifyVerbLength(blocks, "long"), kids, form, king) as T.PsString[], short: compileVPPs(specifyVerbLength(blocks, "short"), kids, form, king) as T.PsString[], + ..."mini" in verbBlock.block.ps ? { + mini: compileVPPs(specifyVerbLength(blocks, "mini"), kids, form, king) as T.PsString[], + } : {}, }; } const subjectPerson = getSubjectSelectionFromBlocks(blocks) @@ -94,6 +98,7 @@ function compileVPPs(blocks: T.Block[][], kids: T.Kid[], form: T.FormVersion, ki kids, false, ); + console.log({ blocksWKids }); return removeDuplicates(combineIntoText(blocksWKids, subjectPerson, {})); } @@ -198,7 +203,7 @@ function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsSt return [piece.block.ps]; } if (piece.block.type === "verbComplement") { - return [{ p: "---", f: "---"}]; //getPashtoFromRendered(piece.block.complement); + return [{ p: "____", f: "____"}]; //getPashtoFromRendered(piece.block.complement); } if (piece.block.type === "objectSelection") { if (typeof piece.block.selection !== "object") { @@ -208,7 +213,11 @@ function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsSt } if (piece.block.type === "verb") { // getLong is just for type safety - we will have split up the length options earlier in compileVPPs - return getLong(piece.block.block.ps); + const verbPs = getLong(piece.block.block.ps); + if (piece.block.block.complement) { + return combineComplementWVerbPs(piece.block.block.complement, verbPs); + } + return verbPs; } if (piece.block.type === "perfectParticipleBlock") { // getLong is just for type safety - we will have split up the length options earlier in compileVPPs @@ -226,6 +235,13 @@ function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsSt // just using the short one for now - it will only be short anyways return getShort(piece.block.ps); } + if (piece.block.type === "complement") { + if (piece.block.selection.type === "sandwich") { + // TODO: Kinda cheating + return getPashtoFromRendered({ type: "AP", selection: piece.block.selection }, false); + } + return piece.block.selection.ps; + } } if ("kid" in piece) { if (piece.kid.type === "ba") { @@ -235,10 +251,18 @@ function getPsFromPiece(piece: T.Block | T.Kid, subjectPerson: T.Person): T.PsSt return [piece.kid.ps]; } } - throw new Error("unrecognized piece type"); } +function combineComplementWVerbPs(comp: T.Rendered, v: T.PsString[]): T.PsString[] { + const compPs = comp.selection.type === "sandwich" + ? getPashtoFromRendered({ type: "AP", selection: comp.selection }, false) + : comp.selection.ps; + return compPs.flatMap((c) => ( + v.map((p) => concatPsString(c, " ", p)) + )); +} + function getEngAPs(blocks: T.Block[][]): string { return getAPsFromBlocks(blocks).reduce((accum, curr) => { const e = getEnglishFromRendered(curr); @@ -247,6 +271,17 @@ function getEngAPs(blocks: T.Block[][]): string { }, ""); } +function getEngComplement(blocks: T.Block[][]): string | undefined { + const comp = getComplementFromBlocks(blocks); + if (!comp) return undefined; + if (comp.selection === undefined) { + return "____"; + } + if (comp.selection.type === "sandwich") { + return getEnglishFromRendered({ type: "AP", selection: comp.selection }); + } +} + 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]; @@ -261,8 +296,13 @@ function putKidsInKidsSection(blocksWVars: T.Block[][], kids: T.Kid[], enforceKi } 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; + function insertEWords(e: string, { subject, object, APs, complement }: { subject: string, object?: string, APs: string, complement: string | undefined }): string { + return e + .replace("$SUBJ", subject) + .replace("$OBJ", object || "") + // TODO: check if this always works + + (complement ? ` ${complement}` : "") + + APs; } const engSubj = getSubjectSelectionFromBlocks(VP.blocks).selection; const obj = getObjectSelectionFromBlocks(VP.blocks).selection; @@ -272,6 +312,7 @@ function compileEnglishVP(VP: T.VPRendered): string[] | undefined { ? "" : undefined; const engAPs = getEngAPs(VP.blocks); + const engComplement = getEngComplement(VP.blocks); // require all English parts for making the English phrase return (VP.englishBase && engSubj && engObj !== undefined) ? VP.englishBase.map(e => insertEWords(e, { @@ -279,6 +320,7 @@ function compileEnglishVP(VP: T.VPRendered): string[] | undefined { subject: getEnglishFromRendered(engSubj) || "", object: engObj ? getEnglishFromRendered(engObj) : "", APs: engAPs, + complement: engComplement, })).map(capitalizeFirstLetter) : undefined; } diff --git a/src/lib/phrase-building/english-vp-rendering.ts b/src/lib/phrase-building/english-vp-rendering.ts index 77801c3..ba294d0 100644 --- a/src/lib/phrase-building/english-vp-rendering.ts +++ b/src/lib/phrase-building/english-vp-rendering.ts @@ -41,9 +41,6 @@ export function renderEnglishVPBase({ subjectPerson, object, vs }: { function isToBe(v: T.EnglishVerbConjugationEc): boolean { return (v[2] === "being"); } - const futureEngBuilder: T.EnglishBuilder = (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([ - `$SUBJ will${n ? " not" : ""} ${isToBe(ec) ? "be" : ec[0]}`, - ]); // TODO: Pull these out to a seperate entity and import it const basicBuilders: Record< T.VerbTense, @@ -59,8 +56,13 @@ export function renderEnglishVPBase({ subjectPerson, object, vs }: { `that $SUBJ ${n ? " won't" : " will"} ${isToBe(ec) ? "be" : ec[0]}`, `$SUBJ ${n ? " not" : ""} should ${isToBe(ec) ? "be" : ec[0]}`, ]), - imperfectiveFuture: futureEngBuilder, - perfectiveFuture: futureEngBuilder, + imperfectiveFuture: (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([ + `$SUBJ will${n ? " not" : ""} ${isToBe(ec) ? "be" : ec[0]}`, + `$SUBJ will${n ? " not" : ""} be ${isToBe(ec) ? "be" : ec[2]}`, + ]), + perfectiveFuture: (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([ + `$SUBJ will${n ? " not" : ""} ${isToBe(ec) ? "be" : ec[0]}`, + ]), imperfectivePast: (s: T.Person, ec: T.EnglishVerbConjugationEc, n: boolean) => ([ // - subj pastEquative (N && "not") ec.2 obj `$SUBJ ${engEquative("past", s)}${n ? " not" : ""} ${ec[2]}`, diff --git a/src/lib/phrase-building/np-tools.ts b/src/lib/phrase-building/np-tools.ts index 7735c2a..e4bc4dc 100644 --- a/src/lib/phrase-building/np-tools.ts +++ b/src/lib/phrase-building/np-tools.ts @@ -5,7 +5,7 @@ import { import * as T from "../../types"; import { concatPsString } from "../p-text-helpers"; -function getBaseAndAdjectives({ selection }: T.Rendered): T.PsString[] { +function getBaseAndAdjectives({ selection }: T.Rendered): T.PsString[] { if (selection.type === "sandwich") { return getSandwichPsBaseAndAdjectives(selection); } @@ -79,7 +79,7 @@ function trimOffShrunkenPossesive(p: T.Rendered): T.Rendered | T.Rendered | T.Rendered, subjectsPerson: false | T.Person): T.PsString[] { +export function getPashtoFromRendered(b: T.Rendered | T.Rendered | T.Rendered, subjectsPerson: false | T.Person): T.PsString[] { const base = getBaseAndAdjectives(b); if (b.selection.type === "loc. adv." || b.selection.type === "adverb") { return base; @@ -88,6 +88,7 @@ export function getPashtoFromRendered(b: T.Rendered | T.Rendered< if (!b.selection.sandwich) { return base } + // TODO: Kinda cheating const sandwichPs = getPashtoFromRendered({ type: "AP", selection: b.selection.sandwich }, false); return base.flatMap(p => ( sandwichPs.flatMap(s => ( @@ -199,7 +200,10 @@ function pronounPossEng(p: T.Person): string { return "their"; } -export function getEnglishFromRendered(r: T.Rendered): string | undefined { +export function getEnglishFromRendered(r: T.Rendered>): string | undefined { + if (r.type === "sandwich") { + return getEnglishFromRenderedSandwich(r); + } if (r.selection.type === "sandwich") { return getEnglishFromRenderedSandwich(r.selection); } diff --git a/src/lib/phrase-building/render-adj.ts b/src/lib/phrase-building/render-adj.ts index 7a0d7d8..162ae31 100644 --- a/src/lib/phrase-building/render-adj.ts +++ b/src/lib/phrase-building/render-adj.ts @@ -18,30 +18,29 @@ function chooseInflection(inflections: T.UnisexSet, pers: T.Per return inflections[gender][infNumber]; } -export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Person, inflected: boolean, role: "king" | "servant" | "none"): T.Rendered { +export function inflectAdjective(a: T.AdjectiveSelection, person: T.Person, inflected: boolean): T.ArrayOneOrMore { const infs = inflectWord(a.entry); + if (!infs) { + return [psStringFromEntry(a.entry)]; + } + if (!infs.inflections || !isUnisexSet(infs.inflections)) { + throw new Error("error getting inflections for adjective, looks like a noun's inflections"); + } + return chooseInflection(infs.inflections, person, inflected); +} + +export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Person, inflected: boolean): T.Rendered { const eWord = getEnglishWord(a.entry); const e = !eWord ? undefined : typeof eWord === "string" ? eWord : (eWord.singular || undefined); - if (!infs) return { - type: "adjective", - entry: a.entry, - ps: [psStringFromEntry(a.entry)], - e, - inflected, - sandwich: a.sandwich ? renderSandwich(a.sandwich) : undefined, - person, - } - if (!infs.inflections || !isUnisexSet(infs.inflections)) { - throw new Error("error getting inflections for adjective, looks like a noun's inflections"); - } + const ps = inflectAdjective(a, person, inflected); return { type: "adjective", entry: a.entry, - ps: chooseInflection(infs.inflections, person, inflected), + ps, e, inflected, person, diff --git a/src/lib/phrase-building/render-common.ts b/src/lib/phrase-building/render-common.ts index abdb3f5..a68b4dd 100644 --- a/src/lib/phrase-building/render-common.ts +++ b/src/lib/phrase-building/render-common.ts @@ -32,7 +32,7 @@ export function findPossesivesToShrink( ]; } if (block.type === "predicateSelection") { - if (block.selection.type === "EQComp") { + if (block.selection.type === "complement") { if (block.selection.selection.type === "sandwich") { return [ ...kids, diff --git a/src/lib/phrase-building/render-complement.ts b/src/lib/phrase-building/render-complement.ts new file mode 100644 index 0000000..2184396 --- /dev/null +++ b/src/lib/phrase-building/render-complement.ts @@ -0,0 +1,55 @@ +import * as T from "../../types"; +import { renderNounSelection } from "./render-np"; +import { getEnglishWord } from "../get-english-word"; +import { psStringFromEntry } from "../p-text-helpers"; +import { renderAdjectiveSelection } from "./render-adj"; +import { renderSandwich } from "./render-sandwich"; + +export function renderComplementSelection(s: T.ComplementSelection | T.UnselectedComplementSelection, person: T.Person): T.Rendered { + if (s.selection.type === "unselected") { + return { + type: "complement", + selection: { + type: "unselected", + ps: [{ p: "____", f: "____" }], + e: "____", + }, + }; + } + if (s.selection.type === "sandwich") { + return { + type: "complement", + selection: renderSandwich(s.selection), + }; + } + const e = getEnglishWord(s.selection.entry); + if (!e || typeof e !== "string") { + throw new Error("error getting english for compliment"); + } + if (s.selection.type === "loc. adv.") { + return { + type: "complement", + selection: { + type: "loc. adv.", + entry: s.selection.entry, + ps: [psStringFromEntry(s.selection.entry)], + e, + inflected: false, + // TODO: don't use persons for these + person, + role: "none", + }, + }; + } + if (s.selection.type === "adjective") { + return { + type: "complement", + selection: renderAdjectiveSelection(s.selection, person, false), + }; + } + // if (s.selection.type === "noun") { + return { + type: "complement", + selection: renderNounSelection(s.selection, false, "none"), + }; +} \ 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 5416c14..5c592ea 100644 --- a/src/lib/phrase-building/render-ep.ts +++ b/src/lib/phrase-building/render-ep.ts @@ -8,12 +8,11 @@ import { getPersonFromVerbForm } from "../../lib/misc-helpers"; import { getVerbBlockPosFromPerson } from "../misc-helpers"; import { getEnglishWord } from "../get-english-word"; import { psStringFromEntry } from "../p-text-helpers"; -import { isLocativeAdverbEntry } from "../type-predicates"; -import { renderAdjectiveSelection } from "./render-adj"; import { renderSandwich } from "./render-sandwich"; import { EPSBlocksAreComplete, getSubjectSelection, makeBlock, makeKid } from "./blocks-utils"; import { removeAccentsWLength } from "../accent-helpers"; import { findPossesivesToShrink, orderKids } from "./render-common"; +import { renderComplementSelection } from "./render-complement"; export function renderEP(EP: T.EPSelectionComplete): T.EPRendered { const { kids, blocks, englishEquativePerson } = getEPSBlocksAndKids(EP); @@ -45,7 +44,8 @@ function getEPSBlocksAndKids(EP: T.EPSelectionComplete): { kids: T.Kid[], blocks type: "predicateSelection", selection: EP.predicate.selection.type === "NP" ? renderNPSelection(EP.predicate.selection, false, false, "subject", "king") - : renderEqCompSelection(EP.predicate.selection, commandingPerson), + // we won't have an unselected complement in the EP - TODO: make safer? + : renderComplementSelection(EP.predicate.selection, commandingPerson) as T.Rendered, }), makeBlock(equative), ], EP.equative.negative); @@ -150,40 +150,6 @@ export function renderAdverbSelection(a: T.AdverbSelection): T.Rendered { - if (s.selection.type === "sandwich") { - return { - type: "EQComp", - selection: renderSandwich(s.selection), - }; - } - const e = getEnglishWord(s.selection.entry); - if (!e || typeof e !== "string") { - throw new Error("error getting english for compliment"); - } - if (isLocativeAdverbEntry(s.selection.entry)) { - return { - type: "EQComp", - selection: { - type: "loc. adv.", - entry: s.selection.entry, - ps: [psStringFromEntry(s.selection.entry)], - e, - inflected: false, - person, - role: "none", - }, - }; - } - if (s.selection.type === "adjective") { - return { - type: "EQComp", - selection: renderAdjectiveSelection(s.selection, person, false, "none"), - }; - } - throw new Error("invalid EqCompSelection"); -} - const equativeBuilders: Record string[]> = { present: (p, n) => { return [ diff --git a/src/lib/phrase-building/render-np.ts b/src/lib/phrase-building/render-np.ts index 95c1e7e..01bf7fd 100644 --- a/src/lib/phrase-building/render-np.ts +++ b/src/lib/phrase-building/render-np.ts @@ -46,7 +46,7 @@ export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflect throw new Error("unknown NP type"); }; -function renderNounSelection(n: T.NounSelection, inflected: boolean, role: "servant" | "king" | "none"): T.Rendered { +export function renderNounSelection(n: T.NounSelection, inflected: boolean, role: "servant" | "king" | "none"): T.Rendered { const english = getEnglishFromNoun(n.entry, n.number); const pashto = ((): T.PsString[] => { const infs = inflectWord(n.entry); @@ -64,7 +64,7 @@ function renderNounSelection(n: T.NounSelection, inflected: boolean, role: "serv const person = getPersonNumber(n.gender, n.number); return { ...n, - adjectives: n.adjectives.map(a => renderAdjectiveSelection(a, person, inflected, role)), + adjectives: n.adjectives.map(a => renderAdjectiveSelection(a, person, inflected)), person, inflected, role, diff --git a/src/lib/phrase-building/render-vp.ts b/src/lib/phrase-building/render-vp.ts index 692c0e9..e36f370 100644 --- a/src/lib/phrase-building/render-vp.ts +++ b/src/lib/phrase-building/render-vp.ts @@ -29,6 +29,7 @@ import { renderNPSelection } from "./render-np"; import { findPerfectiveHead, getObjectSelection, getSubjectSelection, makeBlock, makeKid } from "./blocks-utils"; import { renderAPSelection } from "./render-ap"; import { findPossesivesToShrink, orderKids, getMiniPronounPs } from "./render-common"; +import { renderComplementSelection } from "./render-complement"; // TODO: ISSUE GETTING SPLIT HEAD NOT MATCHING WITH FUTURE VERBS @@ -42,6 +43,7 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered { const kingPerson = getPersonFromNP( king === "subject" ? subject : object, ); + const complementPerson = getPersonFromNP(object ? object : subject) // TODO: more elegant way of handling this type safety if (kingPerson === undefined) { throw new Error("king of sentance does not exist"); @@ -56,8 +58,9 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered { inflectSubject, inflectObject, king, + complementPerson, }); - const { verbBlocks, hasBa } = renderVerbSelection(VP.verb, kingPerson, objectPerson); + const { verbBlocks, hasBa } = renderVerbSelection(VP.verb, kingPerson, objectPerson, VP.externalComplement); const b: T.VPRendered = { type: "VPRendered", king, @@ -332,6 +335,7 @@ function renderVPBlocks(blocks: T.VPSBlockComplete[], config: { inflectSubject: boolean, inflectObject: boolean, king: "subject" | "object", + complementPerson: T.Person | undefined, }): T.Block[] { return blocks.reduce((blocks, { block }): T.Block[] => { if (block.type === "subjectSelection") { @@ -363,9 +367,20 @@ function renderVPBlocks(blocks: T.VPSBlockComplete[], config: { }), ]; } + if (block.type === "AP") { + return [ + ...blocks, + makeBlock(renderAPSelection(block)), + ]; + } return [ ...blocks, - makeBlock(renderAPSelection(block)), + makeBlock( + renderComplementSelection( + block, + // just for typesafety // TODO: only include the person if we're doing an adjective + config.complementPerson || T.Person.FirstSingMale, + )), ]; }, [] as T.Block[]); } @@ -390,7 +405,7 @@ type VerbBlocks = | [T.PerfectParticipleBlock, T.PerfectEquativeBlock] // perfect verb | [T.ModalVerbBlock, T.ModalVerbKedulPart] // modal verb -function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): { +function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, complementPerson: T.Person | undefined, externalComplement: T.ComplementSelection | T.UnselectedComplementSelection | undefined): { verbBlocks: VerbBlocks hasBa: boolean, } { @@ -404,7 +419,9 @@ function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, obje : "stative" in conjugations ? conjugations.stative : conjugations; - const { ps: { head, rest }, hasBa } = getPsVerbConjugation(conj, vs, person, objectPerson); + const { ps: { head, rest }, hasBa } = getPsVerbConjugation(conj, vs, person, complementPerson); + const perfective = isPerfective(vs.tense); + const stativeHelper = isStativeHelper(vs.verb) const vrb: T.VerbRenderedBlock = { type: "verb", block: { @@ -412,11 +429,14 @@ function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, obje ps: rest, person, hasBa, + complement: (!perfective && stativeHelper && externalComplement) + ? renderComplementSelection(externalComplement, complementPerson || T.Person.FirstSingMale) + : undefined, }, }; const verbBlocks = [ ...(head ? ( - vs.isCompound === "stative" ? [{ + (vs.isCompound === "stative" && !vrb.block.complement) ? [{ type: "verbComplement", complement: head, } as T.VerbComplementBlock] : [{ @@ -424,6 +444,9 @@ function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, obje ps: head, } as T.PerfectiveHeadBlock] ) : [] as [T.VerbComplementBlock] | [T.PerfectiveHeadBlock] | []), + ...(externalComplement && perfective && stativeHelper) + ? [renderComplementSelection(externalComplement, complementPerson || T.Person.FirstSingMale)] + : [], ...splitUpIfModal(vrb), ] as VerbBlocks; const perfectStuff = isPerfectTense(vrb.block.tense) ? getPerfectStuff(rest, vrb) : undefined; @@ -433,6 +456,12 @@ function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, obje }; } +function isStativeHelper(v: T.VerbEntry): boolean { + if (v.entry.p === "کول" && v.entry.e.includes("make")) return true; + if (v.entry.p === "کېدل" && v.entry.e.includes("become")) return true; + return false; +} + function splitUpIfModal(v: T.VerbRenderedBlock): [T.VerbRenderedBlock] | [T.ModalVerbBlock, T.ModalVerbKedulPart] { if (!isModalTense(v.block.tense)) { return [v]; @@ -489,7 +518,7 @@ function getPsVerbConjugation(conj: T.VerbConjugation, vs: T.VerbSelectionComple if (perfective) { const past = isPastTense(vs.tense); const splitInfo = conj.info[(past || isModalTense(vs.tense)) ? "root" : "stem"].perfectiveSplit; - if (!splitInfo) return { ps: { head: undefined, rest: verbForm }, hasBa }; + if (!splitInfo) return { ps: { head: undefined, rest: removeBaFromForm(verbForm) }, hasBa }; // TODO: Either solve this in the inflector or here, it seems silly (or redundant) // to have a length option in the perfective split stem?? const [splitHead] = getLong(getMatrixBlock(splitInfo, objectPerson, person)); diff --git a/src/types.ts b/src/types.ts index 401eceb..3db9dbe 100644 --- a/src/types.ts +++ b/src/types.ts @@ -554,7 +554,7 @@ export type SubjectSelectionComplete = { export type PredicateSelectionComplete = { type: "predicateSelection", - selection: EqCompSelection | NPSelection, + selection: ComplementSelection | NPSelection, }; export type ObjectSelectionComplete = { @@ -565,12 +565,14 @@ export type ObjectSelectionComplete = { export type VPSelectionState = { blocks: VPSBlock[] verb: VerbSelection, + externalComplement: undefined | UnselectedComplementSelection | ComplementSelection, form: FormVersion, }; export type VPSelectionComplete = { blocks: VPSBlockComplete[] verb: VerbSelectionComplete, + externalComplement: VPSelectionState["externalComplement"], form: FormVersion, }; @@ -690,19 +692,23 @@ export type RenderedPossesorSelection = { shrunken: boolean, }; +export type UnselectedComplementSelection = { type: "complement", selection: { type: "unselected" }}; + export type Rendered< T extends | NPSelection | NPSelection["selection"] | APSelection | APSelection["selection"] - | EqCompSelection - | EqCompSelection["selection"] | SubjectSelectionComplete | ObjectSelectionComplete | PredicateSelectionComplete | AdjectiveSelection - | SandwichSelection + | SandwichSelection + | ComplementSelection + | ComplementSelection["selection"] + | UnselectedComplementSelection + | undefined > = T extends NPSelection ? { @@ -714,10 +720,19 @@ export type Rendered< type: "AP", selection: Rendered } - : T extends EqCompSelection + : T extends ComplementSelection ? { - type: "EQComp", - selection: Rendered + type: "complement", + selection: Rendered + } + : T extends UnselectedComplementSelection + ? { + type: "complement", + selection: { + type: "unselected", + ps: PsString[], + e: string, + }, } : T extends SandwichSelection ? Omit, "inside"> & { @@ -740,6 +755,11 @@ export type Rendered< inflected: boolean, person: Person, } + : T extends ComplementSelection + ? { + type: "complement", + selection: Rendered, + } : T extends SubjectSelectionComplete ? { type: "subjectSelection", @@ -753,8 +773,13 @@ export type Rendered< : T extends PredicateSelectionComplete ? { type: "predicateSelection", - selection: Rendered | Rendered, - } : ReplaceKey< + selection: Rendered | Rendered, + } : T extends undefined + ? { + type: "undefined", + ps: PsString, + } + : ReplaceKey< Omit, "e", string @@ -777,7 +802,7 @@ export type EPSelectionState = { predicate: { type: "NP" | "Complement", NP: NPSelection | undefined, - Complement: EqCompSelection | undefined, + Complement: ComplementSelection | undefined, }, equative: EquativeSelection, omitSubject: boolean, @@ -795,11 +820,11 @@ export type EPSBlockComplete = { export type VPSBlock = { key: number, // TODO: confusing use of APSelection / should be like APSelection s APSelection complete like the others - block: SubjectSelection | ObjectSelection | (APSelection | undefined), + block: SubjectSelection | ObjectSelection | (APSelection | undefined) | ComplementSelection, }; export type VPSBlockComplete = { key: number, - block: SubjectSelectionComplete | ObjectSelectionComplete | APSelection, + block: SubjectSelectionComplete | ObjectSelectionComplete | APSelection | ComplementSelection, }; export type EPSelectionComplete = Omit & { @@ -808,16 +833,20 @@ export type EPSelectionComplete = Omit omitSubject: boolean, }; -export type EqCompType = "adjective" | "loc. adv." | "sandwich" -export type EqCompSelection = { - type: "EQComp", - selection: AdjectiveSelection | LocativeAdverbSelection | SandwichSelection, -}; +export type ComplementType = "adjective" | "loc. adv." | "sandwich" | "comp. noun"; export type SandwichSelection = S & { inside: NPSelection, }; +export type ComplementSelection = { + type: "complement", + selection: AdjectiveSelection + | LocativeAdverbSelection + | SandwichSelection + | NounSelection, +}; + export type Sandwich = { type: "sandwich", before: PsString | undefined, @@ -898,6 +927,7 @@ export type VerbRenderedBlock = { hasBa: boolean, ps: SingleOrLengthOpts, person: Person, + complement: undefined | Rendered | Rendered, }, }; @@ -907,6 +937,8 @@ export type Block = { | Rendered | Rendered | Rendered + | Rendered + | Rendered | PerfectParticipleBlock | PerfectEquativeBlock | ModalVerbBlock diff --git a/verbs/simple-intrans.js b/verbs/simple-intrans.js index 21cc11b..92b2e41 100644 --- a/verbs/simple-intrans.js +++ b/verbs/simple-intrans.js @@ -46,4 +46,6 @@ module.exports = [ 1527815348, // تلل - to go 1527815216, // راتلل - to come 1527819674, // څملاستل - to lie down -] + 1581086654898, // کېدل - to become + 1527812754, // کېدل - to happen +]; diff --git a/verbs/simple-trans.js b/verbs/simple-trans.js index e3ea24d..6e07f5c 100644 --- a/verbs/simple-trans.js +++ b/verbs/simple-trans.js @@ -87,4 +87,6 @@ module.exports = [ 1527815214, // راوړل - to bring 1527819827, // راوستل - to bring 1527812275, // لیدل - to see -] + 1579015359582, // کول - to make + 1527812752, // کول - to do +];