diff --git a/src/components/src/blocks/Block.tsx b/src/components/src/blocks/Block.tsx index f58443a..5e32c93 100644 --- a/src/components/src/blocks/Block.tsx +++ b/src/components/src/blocks/Block.tsx @@ -146,7 +146,7 @@ function VBBlock({ script: "p" | "f"; block: | T.VBBasic - | T.VBGenNum + | (T.VBBasic & (T.VBPartInfo | T.VBAbilityInfo)) | (T.VBBasic & { person: T.Person; }); @@ -167,8 +167,8 @@ function VBBlock({ ); } const infInfo = - "gender" in block - ? getEnglishGenNumInfo(block.gender, block.number) + "info" in block && block.info.type === "ppart" + ? getEnglishGenNumInfo(block.info.genNum.gender, block.info.genNum.number) : "person" in block ? getEnglishPersonInfo(block.person, "short") : ""; diff --git a/src/components/src/vp-explorer/chart-builder.tsx b/src/components/src/vp-explorer/chart-builder.tsx index 7bb2fcd..32da40e 100644 --- a/src/components/src/vp-explorer/chart-builder.tsx +++ b/src/components/src/vp-explorer/chart-builder.tsx @@ -132,7 +132,7 @@ function grabLength( if (vb.type === "welded") { return { ...vb, - right: grabVBLength(vb.right) as T.VBBasic | T.VBGenNum, + right: grabVBLength(vb.right) as T.VBBasic | T.VBP, }; } if (!(length in vb.ps)) { diff --git a/src/demo-components/ParserDemo.tsx b/src/demo-components/ParserDemo.tsx index d15c045..0d97904 100644 --- a/src/demo-components/ParserDemo.tsx +++ b/src/demo-components/ParserDemo.tsx @@ -3,12 +3,17 @@ import * as T from "../types"; import { parsePhrase } from "../lib/src/parsing/parse-phrase"; import { lookup } from "../lib/src/parsing/lookup"; import { tokenizer } from "../lib/src/parsing/tokenizer"; -import { NPDisplay } from "../components/library"; +import { + CompiledPTextDisplay, + NPDisplay, + compileVP, + renderVP, +} from "../components/library"; function ParserDemo({ opts }: { opts: T.TextOptions }) { const [text, setText] = useState(""); const [result, setResult] = useState< - { inflected: boolean; selection: T.NPSelection }[] + ReturnType["success"] >([]); const [errors, setErrors] = useState([]); function handleChange(e: React.ChangeEvent) { @@ -26,7 +31,7 @@ function ParserDemo({ opts }: { opts: T.TextOptions }) { } return (
-

Type an adjective or noun (w or without adjs) to parse it

+

Type a NP

0 && ( <>
- {errors.map((e) => ( -
{e}
- ))} + {errors.length > 0 ? ( + <> +
possible errors:
+
    + {errors.map((e) => ( +
  • {e}
  • + ))} +
+ + ) : ( +
{errors[0]}
+ )}
Did you mean:
)} - {result.map((np) => ( - - ))} + {result.map((res) => + "inflected" in res ? ( + + ) : "verb" in res ? ( + (() => { + const rendered = renderVP(res); + const compiled = compileVP(rendered, res.form); + return ( +
+ + {compiled.e && ( +
+ {compiled.e.map((e, i) => ( +
{e}
+ ))} +
+ )} +
+ ); + })() + ) : ( + +
{JSON.stringify(res, null, "  ")}
+
+ ) + )}
AST diff --git a/src/lib/src/fp-ps.ts b/src/lib/src/fp-ps.ts index 36a259a..f09b9ce 100644 --- a/src/lib/src/fp-ps.ts +++ b/src/lib/src/fp-ps.ts @@ -92,7 +92,7 @@ export function mapVerbRenderedOutput( f: (a: T.PsString) => T.PsString, [a, b]: T.VerbRenderedOutput ): T.VerbRenderedOutput { - return [fmapVHead(a), fmapV(b)]; + return [fmapVHead(a), fmapVE(b)]; function fmapVHead([v]: [T.VHead] | []): [T.VHead] | [] { if (v === undefined) { return []; @@ -118,10 +118,10 @@ export function mapVerbRenderedOutput( ps: f(comp.ps), }; } - function fmapV(v: [T.VB, T.VBE] | [T.VBE]): [T.VB, T.VBE] | [T.VBE] { - return v.map(fmapVB) as [T.VB, T.VBE] | [T.VBE]; + function fmapVE(v: [T.VBP, T.VBE] | [T.VBE]): [T.VBP, T.VBE] | [T.VBE] { + return v.map(fmapVB) as [T.VBP, T.VBE] | [T.VBE]; } - function fmapVB(v: V): V { + function fmapVB(v: V): V { if (v.type === "welded") { return { ...v, diff --git a/src/lib/src/grammar-units.ts b/src/lib/src/grammar-units.ts index 75e4cd5..290bd4a 100644 --- a/src/lib/src/grammar-units.ts +++ b/src/lib/src/grammar-units.ts @@ -1031,11 +1031,11 @@ export const persons = [ person: 9, }, { - label: { subject: "thay (m. pl.)", object: "them (m. pl.)" }, + label: { subject: "they (m. pl.)", object: "them (m. pl.)" }, person: 10, }, { - label: { subject: "thay (f. pl.)", object: "them (f. pl.)" }, + label: { subject: "they (f. pl.)", object: "them (f. pl.)" }, person: 11, }, ]; diff --git a/src/lib/src/new-verb-engine/render-verb.test.ts b/src/lib/src/new-verb-engine/render-verb.test.ts index ad254fa..d6c2ef3 100644 --- a/src/lib/src/new-verb-engine/render-verb.test.ts +++ b/src/lib/src/new-verb-engine/render-verb.test.ts @@ -1,60 +1,9 @@ +/* eslint-disable jest/valid-title */ import { renderVerb } from "./render-verb"; import { vEntry } from "./rs-helpers"; +import { wordQuery } from "../parsing/lookup"; import * as T from "../../../types"; -const wahul = vEntry({ - ts: 1527815399, - i: 15049, - p: "وهل", - f: "wahul", - g: "wahul", - e: "to hit", - r: 4, - c: "v. trans.", - tppp: "واهه", - tppf: "waahu", - ec: "hit,hits,hitting,hit,hit", -}); -const raawrul = vEntry({ - ts: 1527815214, - i: 6954, - p: "راوړل", - f: "raawRúl", - g: "raawRul", - e: "to bring, deliver (inanimate objects)", - r: 4, - c: "v. trans.", - tppp: "راووړ", - tppf: "raawoR", - noOo: true, - separationAtP: 2, - separationAtF: 3, - ec: "bring,brings,bringing,brought,brought", -}); -const achawul = vEntry({ - ts: 1527811872, - i: 224, - p: "اچول", - f: "achawul", - g: "achawul", - e: "to put, pour, drop, throw, put on", - r: 4, - c: "v. trans.", - ec: "put,puts,putting,put,put", -}); -const ganul = vEntry({ - ts: 1527812000, - i: 11398, - p: "ګڼل", - f: "gaNul, guNul", - g: "gaNul,guNul", - e: "to count, consider, reckon, suppose, assume", - r: 4, - c: "v. trans.", - tppp: "ګاڼه", - tppf: "gaaNu", - ec: "deem", -}); const kawulStat = vEntry({ ts: 1579015359582, i: 11030, @@ -131,570 +80,923 @@ const kedulDyn = vEntry({ separationAtP: 1, separationAtF: 2, }); -const raatlul = vEntry({ - ts: 1527815216, - i: 6875, - p: "راتلل", - f: "raatlúl", - g: "raatlul", - e: "to come", - r: 4, - c: "v. intrans.", - psp: "راځ", - psf: "raadz", - ssp: "راش", - ssf: "ráash", - prp: "راغلل", - prf: "ráaghlul", - pprtp: "راغلی", - pprtf: "raaghúlay", - tppp: "راغی", - tppf: "ráaghay", - noOo: true, - separationAtP: 2, - separationAtF: 3, - ec: "come,comes,coming,came,come", -}); -const wartlul = vEntry({ - ts: 1585228579997, - i: 14821, - p: "ورتلل", - f: "wărtlul", - g: "wartlul", - e: "to come / go over to (third person or place)", - r: 4, - c: "v. intrans.", - psp: "ورځ", - psf: "wărdz", - ssp: "ورش", - ssf: "wársh", - prp: "ورغلل", - prf: "wárghlul", - pprtp: "ورغلی", - pprtf: "wărghúlay", - tppp: "ورغی", - tppf: "wărghay", - noOo: true, - separationAtP: 2, - separationAtF: 3, - ec: "come,comes,coming,came,come", -}); -const osedul = vEntry({ - ts: 1527815139, - i: 1127, - p: "اوسېدل", - f: "osedul", - g: "osedul", - e: "to live, reside, stay, be", - r: 4, - c: "v. intrans.", - shortIntrans: true, - diacExcept: true, -}); -const tlul = vEntry({ - ts: 1527815348, - i: 3791, - p: "تلل", - f: "tlul", - g: "tlul", - e: "to go", - r: 4, - c: "v. intrans.", - psp: "ځ", - psf: "dz", - ssp: "لاړ ش", - ssf: "láaR sh", - prp: "لاړ", - prf: "láaR", - ec: "go,goes,going,went,gone", -}); -const awuxtul = vEntry({ - ts: 1527814012, - i: 1133, - p: "اوښتل", - f: "awUxtul", - g: "awUxtul", - e: "to pass over, overturn, be flipped over, spill over, shift, change, diverge, pass, cross, abandon; to be sprained", - r: 4, - c: "v. intrans.", - psp: "اوړ", - psf: "awR", - ec: "pass", - ep: "over", -}); -const khorul = vEntry({ - ts: 1527812790, - i: 6002, - p: "خوړل", - f: "khoRul", - g: "khoRul", - e: "to eat, to bite", - r: 4, - c: "v. trans.", - psp: "خور", - psf: "khor", - tppp: "خوړ", - tppf: "khoR", - ec: "eat,eats,eating,ate,eaten", -}); -const azmoyul = vEntry({ - ts: 1527811605, - i: 468, - p: "ازمویل", - f: "azmoyul", - g: "azmoyul", - e: "to attempt, try; to experiment, test", - r: 4, - c: "v. trans.", - sepOo: true, - ec: "try", -}); -const khatul = vEntry({ - ts: 1527814025, - i: 5677, - p: "ختل", - f: "khatul", - g: "khatul", - e: "to climb, ascend, rise, go up; to fall out, to fall off, to leave/dissapear; to turn out to be ...; to give a sentence (in law)", - r: 3, - c: "v. intrans.", - psp: "خېژ", - psf: "khejz", - tppp: "خوت", - tppf: "khot", - ec: "climb", -}); -const rasedul = vEntry({ - ts: 1527813573, - i: 7057, - p: "رسېدل", - f: "rasedul", - g: "rasedul", - e: "arrive, reach; (fig.) understand, attain to; mature, ripen", - r: 4, - c: "v. intrans.", - shortIntrans: true, - ec: "arrive", -}); -const weshul = vEntry({ - ts: 1527811701, - i: 15106, - p: "وېشل", - f: "weshul", - g: "weshul", - e: "divide, distribute, share", - r: 4, - c: "v. trans.", - ec: "divide", -}); -const watul = vEntry({ - ts: 1527823376, - i: 14759, - p: "وتل", - f: "watul", - g: "watul", - e: "to go out, exit, leave, emerge", - r: 4, - c: "v. intrans.", - psp: "وځ", - psf: "oodz", - tppp: "واته", - tppf: "waatu", - ec: "go,goes,going,went,gone", - ep: "out", -}); -const wurul = vEntry({ - ts: 1527816865, - i: 14903, - p: "وړل", - f: "wuRúl, oRúl, wRul", - g: "wuRul,oRul,wRul", - e: "to take, carry, bear, move (inanimate objects); to win, earn (subjunctive یوسي - yósee or ویسي - wéesee, simple past یو یې وړلو - yo ye wRulo)", - r: 3, - c: "v. trans.", - ssp: "یوس", - ssf: "yos", - prp: "یوړل", - prf: "yóRul", - tppp: "یوړ", - tppf: "yoR", - noOo: true, - separationAtP: 2, - separationAtF: 2, - diacExcept: true, - ec: "take,takes,taking,took,taken", -}); -const kexodul = vEntry({ - ts: 1527812284, - i: 11113, - p: "کېښودل", - f: "kexodul", - g: "kexodul", - e: "to put, to put down, to set in place", - r: 4, - c: "v. trans.", - psp: "ږد", - psf: "Gd", - ssp: "کېږد", - ssf: "kéGd", - noOo: true, - separationAtP: 2, - separationAtF: 2, - ec: "put,puts,putting,put,put", -}); -const kenaastul = vEntry({ - ts: 1527812759, - i: 11124, - p: "کېناستل", - f: "kenaastul", - g: "kenaastul", - e: "to sit down, to have a seat", - r: 4, - c: "v. intrans.", - psp: "کېن", - psf: "ken", - noOo: true, - separationAtP: 2, - separationAtF: 2, - ec: "sit,sits,sitting,sat", - ep: "down", -}); -const ghadzedul = vEntry({ - ts: 1527812615, - i: 9500, - p: "غځېدل", - f: "ghadzedul", - g: "ghadzedul", - e: "stretch out, lie, be extended, expand", - r: 3, - c: "v. intrans.", - ec: "stretch", - ep: "out", -}); -const prexodul = vEntry({ - ts: 1527815190, - i: 2495, - p: "پرېښودل", - f: "prexodúl", - g: "prexodul", - e: "to leave, abandon, forsake, let go, allow", - r: 4, - c: "v. trans.", - psp: "پرېږد", - psf: "preGd", - noOo: true, - separationAtP: 3, - separationAtF: 3, - ec: "abandon", -}); -const raawustul = vEntry({ - ts: 1527819827, - i: 6955, - p: "راوستل", - f: "raawustúl", - g: "raawustul", - e: "to bring, deliver (animate objects), obtain, extract", - r: 3, - c: "v. trans.", - psp: "راول", - psf: "raawul", - noOo: true, - separationAtP: 2, - separationAtF: 3, - ec: "bring,brings,bringing,brought,brought", -}); -const leedul = vEntry({ - ts: 1527812275, - i: 12049, - p: "لیدل", - f: "leedul", - g: "leedul", - e: "to see", - r: 4, - c: "v. trans./gramm. trans.", - psp: "وین", - psf: "ween", - tppp: "لید", - tppf: "leed", - ec: "see,sees,seeing,saw,seen", -}); -const bandawul = vEntry( - { - ts: 1527821309, - i: 1792, - p: "بندول", - f: "bandawul", - g: "bandawul", - e: "to close, block, stop, barricade, cut off, restrain, hold back", - r: 3, - c: "v. stat. comp. trans.", - l: 1577301753727, - ec: "close", - }, - { - ts: 1577301753727, - i: 1780, - p: "بند", - f: "band", - g: "band", - e: "closed, blocked, stopped", - c: "adj.", - } -); -const bandedul = vEntry( - { - ts: 1588781671306, - i: 1796, - p: "بندېدل", - f: "bandedúl", - g: "bandedul", - e: "to be closed, blocked, stopped", - r: 4, - c: "v. stat. comp. intrans.", - l: 1577301753727, - ec: "be", - ep: "closed", - }, - { - ts: 1577301753727, - i: 1780, - p: "بند", - f: "band", - g: "band", - e: "closed, blocked, stopped", - c: "adj.", - } -); -const sturayKawul = vEntry( - { - ts: 1591033078746, - i: 7877, - p: "ستړی کول", - f: "stuRay kawul", - g: "stuRaykawul", - e: "to make tired, wear out", - r: 4, - c: "v. stat. comp. trans.", - l: 1527815306, - ec: "make", - ep: "tired", - }, - { - ts: 1527815306, - i: 7876, - p: "ستړی", - f: "stúRay", - g: "stuRay", - e: "tired", - r: 4, - c: "adj. / adv.", - } -); -const sturayKedul = vEntry( - { - ts: 1591033069786, - i: 7878, - p: "ستړی کېدل", - f: "stuRay kedul", - g: "stuRaykedul", - e: "to get tired, fatigued", - r: 4, - c: "v. stat. comp. intrans.", - l: 1527815306, - ec: "get", - ep: "tired", - }, - { - ts: 1527815306, - i: 7876, - p: "ستړی", - f: "stúRay", - g: "stuRay", - e: "tired", - r: 4, - c: "adj. / adv.", - } -); +const wahul = wordQuery("وهل", "verb"); +const achawul = wordQuery("اچول", "verb"); +const ganul = wordQuery("ګڼل", "verb"); +const leedul = wordQuery("لیدل", "verb"); +const raatlul = wordQuery("راتلل", "verb"); +const wartlul = wordQuery("ورتلل", "verb"); +const awuxtul = wordQuery("اوښتل", "verb"); +const khorul = wordQuery("خوړل", "verb"); +const khatul = wordQuery("ختل", "verb"); +const rasedul = wordQuery("رسېدل", "verb"); +const weshul = wordQuery("وېشل", "verb"); +const tlul = wordQuery("تلل", "verb"); +const bandawul = wordQuery("بندول", "verb"); +const sturayKawul = wordQuery("ستړی کول", "verb"); +const raawrul = wordQuery("راوړل", "verb"); const ooPh: T.PH = { type: "PH", ps: { p: "و", f: "óo" } }; -test("basic tenses", () => { - expect( - renderVerb({ - verb: wahul, - tense: "presentVerb", - subject: T.Person.FirstSingMale, - object: T.Person.ThirdSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, +const tests: { + label: string; + cases: { + input: Parameters[0]; + output: ReturnType; + }[]; +}[] = [ + { + label: "basic tenses", + cases: [ + { + input: { + verb: wahul, + tense: "presentVerb", + subject: T.Person.FirstSingMale, + object: T.Person.ThirdSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهم", f: "wahum" }], + person: T.Person.FirstSingMale, + info: { + aspect: "imperfective", + base: "stem", + type: "verb", + verb: wahul, + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "subjunctiveVerb", + subject: T.Person.SecondSingMale, + object: T.Person.ThirdPlurMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [{ type: "PH", ps: { f: "óo", p: "و" } }], + [ + { + type: "VB", + ps: [{ p: "وهې", f: "wahe" }], + person: T.Person.SecondSingMale, + info: { + type: "verb", + aspect: "perfective", + base: "stem", + verb: wahul, + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "habitualPerfectivePast", + subject: T.Person.ThirdSingMale, + object: T.Person.ThirdSingFemale, + voice: "active", + negative: false, + }, + output: { + hasBa: true, + vbs: [ + [{ type: "PH", ps: { f: "óo", p: "و" } }], + [ + { + type: "VB", + ps: { + long: [{ p: "وهله", f: "wahula" }], + short: [{ p: "وهه", f: "waha" }], + }, + person: T.Person.ThirdSingFemale, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: wahul, + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "presentVerb", + subject: T.Person.FirstSingMale, + object: T.Person.ThirdSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهم", f: "wahum" }], + person: T.Person.FirstSingMale, + info: { + type: "verb", + aspect: "imperfective", + base: "stem", + verb: wahul, + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "subjunctiveVerb", + subject: T.Person.SecondSingMale, + object: T.Person.ThirdSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [{ type: "PH", ps: { f: "óo", p: "و" } }], + [ + { + type: "VB", + ps: [{ p: "وهې", f: "wahe" }], + person: T.Person.SecondSingMale, + info: { + type: "verb", + aspect: "perfective", + base: "stem", + verb: wahul, + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "habitualPerfectivePast", + subject: T.Person.FirstSingFemale, + object: T.Person.ThirdSingFemale, + voice: "active", + negative: false, + }, + output: { + hasBa: true, + vbs: [ + [{ type: "PH", ps: { f: "óo", p: "و" } }], + [ + { + type: "VB", + ps: { + long: [{ p: "وهله", f: "wahula" }], + short: [{ p: "وهه", f: "waha" }], + }, + person: T.Person.ThirdSingFemale, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: wahul, + }, + }, + ], + ], + }, + }, + ], + }, + { + label: "basic tenses with inflecting roots/stems", + cases: [ + { + input: { + verb: bandawul, + tense: "subjunctiveVerb", + subject: T.Person.FirstSingMale, + object: T.Person.ThirdSingFemale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [ + { + type: "NComp", + comp: { + type: "AdjComp", + ps: { p: "بنده", f: "bánda" }, + gender: "fem", + number: "singular", + }, + }, + ], + [ + { + type: "VB", + ps: { + long: [{ p: "کړم", f: "kRum" }], + short: [{ p: "کم", f: "kum" }], + }, + person: T.Person.FirstSingMale, + info: { + type: "verb", + aspect: "perfective", + base: "stem", + // TODO: should it be this and not kawul ?? + verb: bandawul, + }, + }, + ], + ], + }, + }, + ], + }, + { + label: "imperative tenses", + cases: [ + { + input: { + verb: wahul, + tense: "imperfectiveImperative", + subject: T.Person.SecondSingMale, + object: T.Person.ThirdSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهه", f: "wahá" }], + person: 2, + info: { + type: "verb", + aspect: "imperfective", + base: "stem", + verb: wahul, + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "perfectiveImperative", + subject: T.Person.SecondSingFemale, + object: T.Person.ThirdSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [ooPh], + [ + { + type: "VB", + ps: [{ p: "وهه", f: "waha" }], + person: 3, + info: { + type: "verb", + verb: wahul, + aspect: "perfective", + base: "stem", + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "imperfectiveImperative", + subject: T.Person.SecondPlurMale, + object: T.Person.ThirdSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهئ", f: "wahéy" }], + person: 8, + info: { + type: "verb", + aspect: "imperfective", + base: "stem", + verb: wahul, + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "perfectiveImperative", + subject: T.Person.SecondPlurFemale, + object: T.Person.ThirdSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [ooPh], + [ + { + type: "VB", + ps: [{ p: "وهئ", f: "wahey" }], + person: 9, + info: { + type: "verb", + base: "stem", + aspect: "perfective", + verb: wahul, + }, + }, + ], + ], + }, + }, + ], + }, + { + label: "ability tenses", + cases: [ + { + input: { + verb: wahul, + tense: "presentVerbModal", + subject: T.Person.FirstSingMale, + object: T.Person.ThirdSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: { + long: [ + { p: "وهلی", f: "wahúlay" }, + { p: "وهلای", f: "wahúlaay" }, + ], + short: [ + { p: "وهی", f: "waháy" }, + { p: "وهای", f: "waháay" }, + ], + }, + info: { + type: "ability", + verb: wahul, + aspect: "imperfective", + }, + }, + { + type: "VB", + ps: [{ p: "شم", f: "shum" }], + person: T.Person.FirstSingMale, + info: { + type: "verb", + aspect: "perfective", + base: "stem", + abilityAux: true, + verb: kedulStat, + }, + }, + ], + ], + }, + }, + ], + }, + { + label: "perfect tenses", + cases: [ + { + input: { + verb: wahul, + tense: "presentPerfect", + subject: T.Person.SecondSingMale, + object: T.Person.FirstSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهلی", f: "wahúlay" }], + info: { + type: "ppart", + verb: wahul, + genNum: { + gender: "masc", + number: "singular", + }, + }, + }, + { + type: "VB", + ps: [{ p: "یم", f: "yum" }], + person: T.Person.FirstSingMale, + info: { + type: "equative", + tense: "present", + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "subjunctivePerfect", + subject: T.Person.ThirdPlurFemale, + object: T.Person.FirstSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهلی", f: "wahúlay" }], + info: { + type: "ppart", + genNum: { + gender: "masc", + number: "singular", + }, + verb: wahul, + }, + }, + { + type: "VB", + ps: [{ p: "وم", f: "wum" }], + person: T.Person.FirstSingMale, + info: { + type: "equative", + tense: "subjunctive", + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "habitualPerfect", + object: T.Person.FirstSingMale, + subject: T.Person.ThirdSingMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهلی", f: "wahúlay" }], + info: { + type: "ppart", + verb: wahul, + genNum: { + gender: "masc", + number: "singular", + }, + }, + }, + { + type: "VB", + ps: [{ p: "یم", f: "yum" }], + person: T.Person.FirstSingMale, + info: { + type: "equative", + tense: "habitual", + }, + }, + ], + ], + }, + }, + ], + }, + { + label: "perfect tenses", + cases: [ + { + input: { + verb: wahul, + tense: "habitualPerfect", + subject: T.Person.FirstPlurMale, + object: T.Person.ThirdPlurMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهلي", f: "wahúlee" }], + info: { + type: "ppart", + verb: wahul, + genNum: { + gender: "masc", + number: "plural", + }, + }, + }, + { + type: "VB", + ps: [{ p: "وي", f: "wee" }], + person: T.Person.ThirdPlurMale, + info: { + type: "equative", + tense: "habitual", + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "futurePerfect", + object: T.Person.FirstSingMale, + subject: T.Person.ThirdPlurMale, + voice: "active", + negative: false, + }, + output: { + hasBa: true, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهلی", f: "wahúlay" }], + info: { + type: "ppart", + verb: wahul, + genNum: { + gender: "masc", + number: "singular", + }, + }, + }, + { + type: "VB", + ps: [{ p: "یم", f: "yum" }], + person: T.Person.FirstSingMale, + info: { + type: "equative", + tense: "habitual", + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "pastPerfect", + subject: T.Person.FirstPlurFemale, + object: T.Person.SecondSingFemale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهلې", f: "wahúle" }], + info: { + type: "ppart", + verb: wahul, + genNum: { + gender: "fem", + number: "singular", + }, + }, + }, + { + type: "VB", + ps: { + long: [{ p: "ولې", f: "wule" }], + short: [{ p: "وې", f: "we" }], + }, + person: T.Person.SecondSingFemale, + info: { + type: "equative", + tense: "past", + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "wouldBePerfect", + subject: T.Person.ThirdSingMale, + object: T.Person.SecondSingFemale, + voice: "active", + negative: false, + }, + output: { + hasBa: true, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهلې", f: "wahúle" }], + info: { + type: "ppart", + verb: wahul, + genNum: { + gender: "fem", + number: "singular", + }, + }, + }, + { + type: "VB", + ps: { + long: [{ p: "ولې", f: "wule" }], + short: [{ p: "وې", f: "we" }], + }, + person: T.Person.SecondSingFemale, + info: { + type: "equative", + tense: "past", + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "pastSubjunctivePerfect", + object: T.Person.SecondSingFemale, + subject: T.Person.FirstPlurMale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهلې", f: "wahúle" }], + info: { + type: "ppart", + verb: wahul, + genNum: { + gender: "fem", + number: "singular", + }, + }, + }, + { + type: "VB", + ps: [ + { p: "وای", f: "waay" }, + { p: "وی", f: "way" }, + ], + person: T.Person.SecondSingFemale, + info: { + type: "equative", + tense: "pastSubjunctive", + }, + }, + ], + ], + }, + }, + { + input: { + verb: wahul, + tense: "wouldHaveBeenPerfect", + subject: T.Person.FirstSingMale, + object: T.Person.SecondSingFemale, + voice: "active", + negative: false, + }, + output: { + hasBa: true, + vbs: [ + [], + [ + { + type: "VB", + ps: [{ p: "وهلې", f: "wahúle" }], + info: { + type: "ppart", + verb: wahul, + genNum: { + gender: "fem", + number: "singular", + }, + }, + }, + { + type: "VB", + ps: [ + { p: "وای", f: "waay" }, + { p: "وی", f: "way" }, + ], + person: T.Person.SecondSingFemale, + info: { + type: "equative", + tense: "pastSubjunctive", + }, + }, + ], + ], + }, + }, + ], + }, + { + label: "ending on complex verbs", + cases: [ + { + input: { + verb: sturayKawul, + tense: "presentVerb", + subject: T.Person.SecondSingMale, + voice: "active", + object: T.Person.ThirdSingFemale, + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "welded", + left: { + type: "NComp", + comp: { + type: "AdjComp", + ps: { p: "ستړې", f: "stuRe" }, + gender: "fem", + number: "singular", + }, + }, + right: { + type: "VB", + ps: [{ p: "کوې", f: "kawé" }], + }, + person: T.Person.SecondSingMale, + info: { + type: "verb", + aspect: "imperfective", + base: "stem", + verb: sturayKawul, + }, + }, + ], + ], + }, + }, + { + input: { + verb: sturayKawul, + tense: "presentVerbModal", + subject: T.Person.SecondSingMale, + object: T.Person.ThirdSingFemale, + voice: "active", + negative: false, + }, + output: { + hasBa: false, + vbs: [ + [], + [ + { + type: "welded", + left: { + type: "NComp", + comp: { + type: "AdjComp", + ps: { p: "ستړې", f: "stuRe" }, + gender: "fem", + number: "singular", + }, + }, + right: { + type: "VB", + ps: { + long: [ + { p: "کولی", f: "kawúlay" }, + { p: "کولای", f: "kawúlaay" }, + ], + short: [ + { p: "کوی", f: "kawáy" }, + { p: "کوای", f: "kawáay" }, + ], + }, + }, + info: { + type: "ability", + verb: sturayKawul, + aspect: "imperfective", + }, + }, + { + type: "VB", + ps: [{ p: "شې", f: "she" }], + person: 2, + info: { + type: "verb", + aspect: "perfective", + base: "stem", + verb: kedulStat, + abilityAux: true, + }, + }, + ], + ], + }, + }, + ], + }, +]; - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهم", f: "wahum" }], - person: T.Person.FirstSingMale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "subjunctiveVerb", - subject: T.Person.SecondSingMale, - object: T.Person.ThirdPlurMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [{ type: "PH", ps: { f: "óo", p: "و" } }], - [ - { - type: "VB", - ps: [{ p: "وهې", f: "wahe" }], - person: T.Person.SecondSingMale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "habitualPerfectivePast", - subject: T.Person.ThirdSingMale, - object: T.Person.ThirdSingFemale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: true, - vbs: [ - [{ type: "PH", ps: { f: "óo", p: "و" } }], - [ - { - type: "VB", - ps: { - long: [{ p: "وهله", f: "wahula" }], - short: [{ p: "وهه", f: "waha" }], - }, - person: T.Person.ThirdSingFemale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "presentVerb", - subject: T.Person.FirstSingMale, - object: T.Person.ThirdSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهم", f: "wahum" }], - person: T.Person.FirstSingMale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "subjunctiveVerb", - subject: T.Person.SecondSingMale, - object: T.Person.ThirdSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [{ type: "PH", ps: { f: "óo", p: "و" } }], - [ - { - type: "VB", - ps: [{ p: "وهې", f: "wahe" }], - person: T.Person.SecondSingMale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "habitualPerfectivePast", - subject: T.Person.FirstSingFemale, - object: T.Person.ThirdSingFemale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: true, - vbs: [ - [{ type: "PH", ps: { f: "óo", p: "و" } }], - [ - { - type: "VB", - ps: { - long: [{ p: "وهله", f: "wahula" }], - short: [{ p: "وهه", f: "waha" }], - }, - person: T.Person.ThirdSingFemale, - }, - ], - ], - }); -}); - -test("basic tenses with inflecting roots/stems", () => { - expect( - renderVerb({ - verb: bandawul, - tense: "subjunctiveVerb", - subject: T.Person.FirstSingMale, - object: T.Person.ThirdSingFemale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [ - { - type: "NComp", - comp: { - type: "AdjComp", - ps: { p: "بنده", f: "bánda" }, - gender: "fem", - number: "singular", - }, - }, - ], - [ - { - type: "VB", - ps: { - long: [{ p: "کړم", f: "kRum" }], - short: [{ p: "کم", f: "kum" }], - }, - person: T.Person.FirstSingMale, - }, - ], - ], +tests.forEach(({ label, cases }) => { + test(label, () => { + cases.forEach(({ input, output }) => { + expect(renderVerb(input)).toEqual(output); + }); }); }); @@ -718,6 +1020,12 @@ test("special endings", () => { short: [{ p: "چاوه", f: "chaawu" }], }, person: 4, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: achawul, + }, }, ], ], @@ -735,6 +1043,12 @@ test("special endings", () => { short: [{ p: "اچاوه", f: "achaawú" }], }, person: 4, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: achawul, + }, }, ], ], @@ -753,6 +1067,12 @@ test("special endings", () => { short: [{ p: "ګاڼه", f: "gaaNu" }], }, person: 4, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: ganul, + }, }, ], ], @@ -770,6 +1090,12 @@ test("special endings", () => { short: [{ p: "ګاڼه", f: "gaaNú" }], }, person: 4, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: ganul, + }, }, ], ], @@ -793,6 +1119,12 @@ test("special endings", () => { ], }, person: 4, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: khatul, + }, }, ], ], @@ -815,6 +1147,12 @@ test("special endings", () => { ], }, person: 4, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: leedul, + }, }, ], ], @@ -832,6 +1170,12 @@ test("special endings", () => { short: [{ p: "ووړ", f: "woR" }], }, person: 4, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: raawrul, + }, }, ], ], @@ -854,6 +1198,12 @@ test("special endings", () => { ], }, person: 4, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: rasedul, + }, }, ], ], @@ -875,6 +1225,12 @@ test("special endings", () => { ], }, person: 4, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: awuxtul, + }, }, ], ], @@ -896,6 +1252,12 @@ test("special endings", () => { ], }, person: 4, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: weshul, + }, }, ], ], @@ -913,6 +1275,12 @@ test("special endings", () => { short: [{ p: "شو", f: "sho" }], }, person: T.Person.ThirdSingMale, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: kedulStat, + }, }, ], ], @@ -930,6 +1298,12 @@ test("special endings", () => { short: [{ p: "کېده", f: "kedú" }], }, person: T.Person.ThirdSingMale, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: kedulStat, + }, }, ], ], @@ -955,6 +1329,12 @@ test("special endings", () => { ], }, person: T.Person.ThirdSingMale, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: kawulStat, + }, }, ], ], @@ -980,6 +1360,12 @@ test("special endings", () => { ], }, person: T.Person.ThirdSingMale, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: kawulDyn, + }, }, ], ], @@ -997,6 +1383,12 @@ test("special endings", () => { short: [{ p: "کاوه", f: "kaawú" }], }, person: T.Person.ThirdSingMale, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: kawulDyn, + }, }, ], ], @@ -1020,6 +1412,12 @@ test("special endings", () => { ], }, person: T.Person.ThirdSingMale, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: tlul, + }, }, ], ], @@ -1040,6 +1438,12 @@ test("special endings", () => { ], }, person: T.Person.ThirdSingMale, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: raatlul, + }, }, ], ], @@ -1054,6 +1458,12 @@ test("special endings", () => { type: "VB", ps: [{ p: "غی", f: "ghay" }], person: T.Person.ThirdSingMale, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: raatlul, + }, }, ], ], @@ -1068,6 +1478,12 @@ test("special endings", () => { type: "VB", ps: [{ p: "غی", f: "ghay" }], person: T.Person.ThirdSingMale, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: wartlul, + }, }, ], ], @@ -1109,6 +1525,12 @@ test("special endings", () => { short: [{ p: "شو", f: "shoo" }], }, person: T.Person.FirstPlurMale, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: kedulStat, + }, }, ], ], @@ -1135,6 +1557,12 @@ test("special endings", () => { short: [{ p: "تلم", f: "tlúm" }], }, person: T.Person.FirstSingMale, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: tlul, + }, }, ], ], @@ -1161,6 +1589,12 @@ test("special endings", () => { short: [{ p: "تله", f: "tlá" }], }, person: T.Person.ThirdSingFemale, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: tlul, + }, }, ], ], @@ -1187,6 +1621,12 @@ test("special endings", () => { short: [{ p: "خوړل", f: "khoRúl" }], }, person: T.Person.ThirdPlurMale, + info: { + type: "verb", + aspect: "imperfective", + base: "root", + verb: khorul, + }, }, ], ], @@ -1212,450 +1652,12 @@ test("special endings", () => { short: [{ p: "خوړل", f: "khoRul" }], }, person: T.Person.ThirdPlurMale, - }, - ], - ], - }); -}); - -test("imperative tenses", () => { - expect( - renderVerb({ - verb: wahul, - tense: "imperfectiveImperative", - subject: T.Person.SecondSingMale, - object: T.Person.ThirdSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [[], [{ type: "VB", ps: [{ p: "وهه", f: "wahá" }], person: 2 }]], - }); - expect( - renderVerb({ - verb: wahul, - tense: "perfectiveImperative", - subject: T.Person.SecondSingFemale, - object: T.Person.ThirdSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [[ooPh], [{ type: "VB", ps: [{ p: "وهه", f: "waha" }], person: 3 }]], - }); - expect( - renderVerb({ - verb: wahul, - tense: "imperfectiveImperative", - subject: T.Person.SecondPlurMale, - object: T.Person.ThirdSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [[], [{ type: "VB", ps: [{ p: "وهئ", f: "wahéy" }], person: 8 }]], - }); - expect( - renderVerb({ - verb: wahul, - tense: "perfectiveImperative", - subject: T.Person.SecondPlurFemale, - object: T.Person.ThirdSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [[ooPh], [{ type: "VB", ps: [{ p: "وهئ", f: "wahey" }], person: 9 }]], - }); -}); - -test("ability tenses", () => { - expect( - renderVerb({ - verb: wahul, - tense: "presentVerbModal", - subject: T.Person.FirstSingMale, - object: T.Person.ThirdSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [], - [ - { - type: "VB", - ps: { - long: [ - { p: "وهلی", f: "wahúlay" }, - { p: "وهلای", f: "wahúlaay" }, - ], - short: [ - { p: "وهی", f: "waháy" }, - { p: "وهای", f: "waháay" }, - ], - }, - }, - { - type: "VB", - ps: [{ p: "شم", f: "shum" }], - person: T.Person.FirstSingMale, - }, - ], - ], - }); -}); - -test("perfect tenses", () => { - expect( - renderVerb({ - verb: wahul, - tense: "presentPerfect", - subject: T.Person.SecondSingMale, - object: T.Person.FirstSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهلی", f: "wahúlay" }], - gender: "masc", - number: "singular", - }, - { - type: "VB", - ps: [{ p: "یم", f: "yum" }], - person: T.Person.FirstSingMale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "subjunctivePerfect", - subject: T.Person.ThirdPlurFemale, - object: T.Person.FirstSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهلی", f: "wahúlay" }], - gender: "masc", - number: "singular", - }, - { - type: "VB", - ps: [{ p: "وم", f: "wum" }], - person: T.Person.FirstSingMale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "habitualPerfect", - object: T.Person.FirstSingMale, - subject: T.Person.ThirdSingMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهلی", f: "wahúlay" }], - gender: "masc", - number: "singular", - }, - { - type: "VB", - ps: [{ p: "یم", f: "yum" }], - person: T.Person.FirstSingMale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "habitualPerfect", - subject: T.Person.FirstPlurMale, - object: T.Person.ThirdPlurMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهلي", f: "wahúlee" }], - gender: "masc", - number: "plural", - }, - { - type: "VB", - ps: [{ p: "وي", f: "wee" }], - person: T.Person.ThirdPlurMale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "futurePerfect", - object: T.Person.FirstSingMale, - subject: T.Person.ThirdPlurMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: true, - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهلی", f: "wahúlay" }], - gender: "masc", - number: "singular", - }, - { - type: "VB", - ps: [{ p: "یم", f: "yum" }], - person: T.Person.FirstSingMale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "pastPerfect", - subject: T.Person.FirstPlurFemale, - object: T.Person.SecondSingFemale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهلې", f: "wahúle" }], - gender: "fem", - number: "singular", - }, - { - type: "VB", - ps: { - long: [{ p: "ولې", f: "wule" }], - short: [{ p: "وې", f: "we" }], - }, - person: T.Person.SecondSingFemale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "wouldBePerfect", - subject: T.Person.ThirdSingMale, - object: T.Person.SecondSingFemale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: true, - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهلې", f: "wahúle" }], - gender: "fem", - number: "singular", - }, - { - type: "VB", - ps: { - long: [{ p: "ولې", f: "wule" }], - short: [{ p: "وې", f: "we" }], - }, - person: T.Person.SecondSingFemale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "pastSubjunctivePerfect", - object: T.Person.SecondSingFemale, - subject: T.Person.FirstPlurMale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهلې", f: "wahúle" }], - gender: "fem", - number: "singular", - }, - { - type: "VB", - ps: [ - { p: "وای", f: "waay" }, - { p: "وی", f: "way" }, - ], - person: T.Person.SecondSingFemale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: wahul, - tense: "wouldHaveBeenPerfect", - subject: T.Person.FirstSingMale, - object: T.Person.SecondSingFemale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: true, - vbs: [ - [], - [ - { - type: "VB", - ps: [{ p: "وهلې", f: "wahúle" }], - gender: "fem", - number: "singular", - }, - { - type: "VB", - ps: [ - { p: "وای", f: "waay" }, - { p: "وی", f: "way" }, - ], - person: T.Person.SecondSingFemale, - }, - ], - ], - }); -}); - -test("ending on complex verbs", () => { - expect( - renderVerb({ - verb: sturayKawul, - tense: "presentVerbModal", - subject: T.Person.SecondSingMale, - object: T.Person.ThirdSingFemale, - voice: "active", - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [], - [ - { - type: "welded", - left: { - type: "NComp", - comp: { - type: "AdjComp", - ps: { p: "ستړې", f: "stuRe" }, - gender: "fem", - number: "singular", - }, - }, - right: { - type: "VB", - ps: { - long: [ - { p: "کولی", f: "kawúlay" }, - { p: "کولای", f: "kawúlaay" }, - ], - short: [ - { p: "کوی", f: "kawáy" }, - { p: "کوای", f: "kawáay" }, - ], - }, - }, - }, - { - type: "VB", - ps: [{ p: "شې", f: "she" }], - person: T.Person.SecondSingMale, - }, - ], - ], - }); - expect( - renderVerb({ - verb: sturayKawul, - tense: "presentVerb", - subject: T.Person.SecondSingMale, - voice: "active", - object: T.Person.ThirdSingFemale, - negative: false, - }) - ).toEqual({ - hasBa: false, - vbs: [ - [], - [ - { - type: "welded", - left: { - type: "NComp", - comp: { - type: "AdjComp", - ps: { p: "ستړې", f: "stuRe" }, - gender: "fem", - number: "singular", - }, - }, - right: { - type: "VB", - ps: [{ p: "کوې", f: "kawé" }], - }, - person: T.Person.SecondSingMale, + info: { + type: "verb", + aspect: "perfective", + base: "root", + verb: khorul, + }, }, ], ], diff --git a/src/lib/src/new-verb-engine/render-verb.ts b/src/lib/src/new-verb-engine/render-verb.ts index 4ed288e..7bebd06 100644 --- a/src/lib/src/new-verb-engine/render-verb.ts +++ b/src/lib/src/new-verb-engine/render-verb.ts @@ -26,6 +26,7 @@ import { getPastParticiple, getRootStem } from "./roots-and-stems"; import { isKedul, perfectTenseToEquative, + vEntry, verbEndingConcat, } from "./rs-helpers"; import { @@ -33,7 +34,24 @@ import { accentPsSyllable, removeAccents, } from "../accent-helpers"; - +const kedulStat = vEntry({ + ts: 1581086654898, + i: 11100, + p: "کېدل", + f: "kedul", + g: "kedul", + e: "to become _____", + r: 2, + c: "v. intrans.", + ssp: "ش", + ssf: "sh", + prp: "شول", + prf: "shwul", + pprtp: "شوی", + pprtf: "shúway", + noOo: true, + ec: "become", +}); const formulas: Record< T.VerbTense | T.ImperativeTense, { @@ -123,11 +141,12 @@ export function renderVerb({ const type = isAbilityTense(tense) ? "ability" : "basic"; const transitive = object !== undefined; const king = transitive && isPast ? object : subject; + const base = isPast ? "root" : "stem"; // #1 get the appropriate root / stem const [vHead, rest] = getRootStem({ verb, - rs: isPast ? "root" : "stem", + rs: base, aspect: negative && isImperativeTense(tense) ? "imperfective" : aspect, voice, type, @@ -148,6 +167,8 @@ export function renderVerb({ pastThird: isPast && king === T.Person.ThirdSingMale, aspect, basicForm: type === "basic" && voice === "active", + base, + ability: type === "ability", }), ], }; @@ -165,7 +186,7 @@ function renderPerfectVerb({ voice: T.Voice; }): { hasBa: boolean; - vbs: [[], [T.VB, T.VBE]]; + vbs: [[], [T.VBP, T.VBE]]; objComp: T.Rendered | undefined; } { const hasBa = perfectTenseHasBa(tense); @@ -178,6 +199,10 @@ function renderPerfectVerb({ type: "VB", person, ps: fmapSingleOrLengthOpts((x) => x[row][col], equative), + info: { + type: "equative", + tense: perfectTenseToEquative(tense), + }, }; return { hasBa, @@ -194,32 +219,46 @@ function addEnding({ pastThird, aspect, basicForm, + base, + ability, }: { - rs: [T.VB, T.VBA] | [T.VBA]; + rs: [T.VBP, T.VB] | [T.VB]; ending: T.SingleOrLengthOpts; person: T.Person; verb: T.VerbEntry; pastThird: boolean; aspect: T.Aspect; basicForm: boolean; -}): [T.VB, T.VBE] | [T.VBE] { + base: "stem" | "root"; + ability: boolean; +}): [T.VBP, T.VBE] | [T.VBE] { return rs.length === 2 ? [rs[0], addEnd(rs[1], ending)] : [addEnd(rs[0], ending)]; - function addEnd( - vba: T.VBA, - ending: T.SingleOrLengthOpts - ): T.VBE { - if (vba.type === "welded") { + function addEnd(vb: T.VB, ending: T.SingleOrLengthOpts): T.VBE { + const info = { + type: "verb" as const, + aspect: ability ? "perfective" : aspect, + base, + verb: ability ? kedulStat : verb, + ...(ability + ? { + abilityAux: true, + } + : {}), + }; + if (vb.type === "welded") { return { - ...vba, - right: addToVBBasicEnd(vba.right, ending), + ...vb, + right: addToVBBasicEnd(vb.right, ending), person, + info, }; } return { - ...addToVBBasicEnd(vba, ending), + ...addToVBBasicEnd(vb, ending), person, + info, }; } function addToVBBasicEnd( diff --git a/src/lib/src/new-verb-engine/roots-and-stems.test.ts b/src/lib/src/new-verb-engine/roots-and-stems.test.ts index 07f0e07..7854780 100644 --- a/src/lib/src/new-verb-engine/roots-and-stems.test.ts +++ b/src/lib/src/new-verb-engine/roots-and-stems.test.ts @@ -1,44 +1,13 @@ +/* eslint-disable jest/valid-title */ import * as T from "../../../types"; import { getPastParticiple, getRootStem } from "./roots-and-stems"; import { vEntry } from "./rs-helpers"; +import { wordQuery } from "../parsing/lookup"; + +const wahul = wordQuery("وهل", "verb"); +const achawul = wordQuery("اچول", "verb"); +const ganul = wordQuery("ګڼل", "verb"); -const wahul = vEntry({ - ts: 1527815399, - i: 15049, - p: "وهل", - f: "wahul", - g: "wahul", - e: "to hit", - r: 4, - c: "v. trans.", - tppp: "واهه", - tppf: "waahu", - ec: "hit,hits,hitting,hit,hit", -}); -const achawul = vEntry({ - ts: 1527811872, - i: 224, - p: "اچول", - f: "achawul", - g: "achawul", - e: "to put, pour, drop, throw, put on", - r: 4, - c: "v. trans.", - ec: "put,puts,putting,put,put", -}); -const ganul = vEntry({ - ts: 1527812000, - i: 11398, - p: "ګڼل", - f: "gaNul, guNul", - g: "gaNul,guNul", - e: "to count, consider, reckon, suppose, assume", - r: 4, - c: "v. trans.", - tppp: "ګاڼه", - tppf: "gaaNu", - ec: "deem", -}); const kawulStat = vEntry({ ts: 1579015359582, i: 11030, @@ -115,463 +84,32 @@ const kedulDyn = vEntry({ separationAtP: 1, separationAtF: 2, }); -const raatlul = vEntry({ - ts: 1527815216, - i: 6875, - p: "راتلل", - f: "raatlúl", - g: "raatlul", - e: "to come", - r: 4, - c: "v. intrans.", - psp: "راځ", - psf: "raadz", - ssp: "راش", - ssf: "ráash", - prp: "راغلل", - prf: "ráaghlul", - pprtp: "راغلی", - pprtf: "raaghúlay", - tppp: "راغی", - tppf: "ráaghay", - noOo: true, - separationAtP: 2, - separationAtF: 3, - ec: "come,comes,coming,came,come", -}); -const wartlul = vEntry({ - ts: 1585228579997, - i: 14821, - p: "ورتلل", - f: "wărtlul", - g: "wartlul", - e: "to come / go over to (third person or place)", - r: 4, - c: "v. intrans.", - psp: "ورځ", - psf: "wărdz", - ssp: "ورش", - ssf: "wársh", - prp: "ورغلل", - prf: "wárghlul", - pprtp: "ورغلی", - pprtf: "wărghúlay", - tppp: "ورغی", - tppf: "wărghay", - noOo: true, - separationAtP: 2, - separationAtF: 3, - ec: "come,comes,coming,came,come", -}); -const osedul = vEntry({ - ts: 1527815139, - i: 1127, - p: "اوسېدل", - f: "osedul", - g: "osedul", - e: "to live, reside, stay, be", - r: 4, - c: "v. intrans.", - shortIntrans: true, - diacExcept: true, -}); -const awuxtul = vEntry({ - ts: 1527814012, - i: 1133, - p: "اوښتل", - f: "awUxtul", - g: "awUxtul", - e: "to pass over, overturn, be flipped over, spill over, shift, change, diverge, pass, cross, abandon; to be sprained", - r: 4, - c: "v. intrans.", - psp: "اوړ", - psf: "awR", - ec: "pass", - ep: "over", -}); -const khorul = vEntry({ - ts: 1527812790, - i: 6002, - p: "خوړل", - f: "khoRul", - g: "khoRul", - e: "to eat, to bite", - r: 4, - c: "v. trans.", - psp: "خور", - psf: "khor", - tppp: "خوړ", - tppf: "khoR", - ec: "eat,eats,eating,ate,eaten", -}); -const azmoyul = vEntry({ - ts: 1527811605, - i: 468, - p: "ازمویل", - f: "azmoyul", - g: "azmoyul", - e: "to attempt, try; to experiment, test", - r: 4, - c: "v. trans.", - sepOo: true, - ec: "try", -}); -const khatul = vEntry({ - ts: 1527814025, - i: 5677, - p: "ختل", - f: "khatul", - g: "khatul", - e: "to climb, ascend, rise, go up; to fall out, to fall off, to leave/dissapear; to turn out to be ...; to give a sentence (in law)", - r: 3, - c: "v. intrans.", - psp: "خېژ", - psf: "khejz", - tppp: "خوت", - tppf: "khot", - ec: "climb", -}); -const rasedul = vEntry({ - ts: 1527813573, - i: 7057, - p: "رسېدل", - f: "rasedul", - g: "rasedul", - e: "arrive, reach; (fig.) understand, attain to; mature, ripen", - r: 4, - c: "v. intrans.", - shortIntrans: true, - ec: "arrive", -}); -const weshul = vEntry({ - ts: 1527811701, - i: 15106, - p: "وېشل", - f: "weshul", - g: "weshul", - e: "divide, distribute, share", - r: 4, - c: "v. trans.", - ec: "divide", -}); -const watul = vEntry({ - ts: 1527823376, - i: 14759, - p: "وتل", - f: "watul", - g: "watul", - e: "to go out, exit, leave, emerge", - r: 4, - c: "v. intrans.", - psp: "وځ", - psf: "oodz", - tppp: "واته", - tppf: "waatu", - ec: "go,goes,going,went,gone", - ep: "out", -}); -const wurul = vEntry({ - ts: 1527816865, - i: 14903, - p: "وړل", - f: "wuRúl, oRúl, wRul", - g: "wuRul,oRul,wRul", - e: "to take, carry, bear, move (inanimate objects); to win, earn (subjunctive یوسي - yósee or ویسي - wéesee, simple past یو یې وړلو - yo ye wRulo)", - r: 3, - c: "v. trans.", - ssp: "یوس", - ssf: "yos", - prp: "یوړل", - prf: "yóRul", - tppp: "یوړ", - tppf: "yoR", - noOo: true, - separationAtP: 2, - separationAtF: 2, - diacExcept: true, - ec: "take,takes,taking,took,taken", -}); -const kexodul = vEntry({ - ts: 1527812284, - i: 11113, - p: "کېښودل", - f: "kexodul", - g: "kexodul", - e: "to put, to put down, to set in place", - r: 4, - c: "v. trans.", - psp: "ږد", - psf: "Gd", - ssp: "کېږد", - ssf: "kéGd", - noOo: true, - separationAtP: 2, - separationAtF: 2, - ec: "put,puts,putting,put,put", -}); -const kenaastul = vEntry({ - ts: 1527812759, - i: 11124, - p: "کېناستل", - f: "kenaastul", - g: "kenaastul", - e: "to sit down, to have a seat", - r: 4, - c: "v. intrans.", - psp: "کېن", - psf: "ken", - noOo: true, - separationAtP: 2, - separationAtF: 2, - ec: "sit,sits,sitting,sat", - ep: "down", -}); -const ghadzedul = vEntry({ - ts: 1527812615, - i: 9500, - p: "غځېدل", - f: "ghadzedul", - g: "ghadzedul", - e: "stretch out, lie, be extended, expand", - r: 3, - c: "v. intrans.", - ec: "stretch", - ep: "out", -}); -const prexodul = vEntry({ - ts: 1527815190, - i: 2495, - p: "پرېښودل", - f: "prexodúl", - g: "prexodul", - e: "to leave, abandon, forsake, let go, allow", - r: 4, - c: "v. trans.", - psp: "پرېږد", - psf: "preGd", - noOo: true, - separationAtP: 3, - separationAtF: 3, - ec: "abandon", -}); -const raawustul = vEntry({ - ts: 1527819827, - i: 6955, - p: "راوستل", - f: "raawustúl", - g: "raawustul", - e: "to bring, deliver (animate objects), obtain, extract", - r: 3, - c: "v. trans.", - psp: "راول", - psf: "raawul", - noOo: true, - separationAtP: 2, - separationAtF: 3, - ec: "bring,brings,bringing,brought,brought", -}); -const tlul = vEntry({ - ts: 1527815348, - i: 3804, - p: "تلل", - f: "tlul", - g: "tlul", - e: "to go", - r: 4, - c: "v. intrans.", - psp: "ځ", - psf: "dz", - ssp: "لاړ ش", - ssf: "láaR sh", - prp: "لاړ", - prf: "láaR", - ec: "go,goes,going,went,gone", -}); -const bandawul = vEntry( - { - ts: 1527821309, - i: 1792, - p: "بندول", - f: "bandawul", - g: "bandawul", - e: "to close, block, stop, barricade, cut off, restrain, hold back", - r: 3, - c: "v. stat. comp. trans.", - l: 1577301753727, - ec: "close", - }, - { - ts: 1577301753727, - i: 1780, - p: "بند", - f: "band", - g: "band", - e: "closed, blocked, stopped", - c: "adj.", - } -); -const bandedul = vEntry( - { - ts: 1588781671306, - i: 1796, - p: "بندېدل", - f: "bandedúl", - g: "bandedul", - e: "to be closed, blocked, stopped", - r: 4, - c: "v. stat. comp. intrans.", - l: 1577301753727, - ec: "be", - ep: "closed", - }, - { - ts: 1577301753727, - i: 1780, - p: "بند", - f: "band", - g: "band", - e: "closed, blocked, stopped", - c: "adj.", - } -); -const sturayKawul = vEntry( - { - ts: 1591033078746, - i: 7877, - p: "ستړی کول", - f: "stuRay kawul", - g: "stuRaykawul", - e: "to make tired, wear out", - r: 4, - c: "v. stat. comp. trans.", - l: 1527815306, - ec: "make", - ep: "tired", - }, - { - ts: 1527815306, - i: 7876, - p: "ستړی", - f: "stúRay", - g: "stuRay", - e: "tired", - r: 4, - c: "adj. / adv.", - } -); -const sturayKedul = vEntry( - { - ts: 1591033069786, - i: 7878, - p: "ستړی کېدل", - f: "stuRay kedul", - g: "stuRaykedul", - e: "to get tired, fatigued", - r: 4, - c: "v. stat. comp. intrans.", - l: 1527815306, - ec: "get", - ep: "tired", - }, - { - ts: 1527815306, - i: 7876, - p: "ستړی", - f: "stúRay", - g: "stuRay", - e: "tired", - r: 4, - c: "adj. / adv.", - } -); -const bayaanedul = vEntry( - { - ts: 1659037345120, - i: 2055, - p: "بیانېدل", - f: "bayaanedúl", - g: "bayaanedul", - e: "to be described, told, narrated, explained, declared", - r: 4, - c: "v. stat. comp. intrans.", - l: 1527814259, - }, - { - ts: 1527814259, - i: 2052, - p: "بیان", - f: "bayáan", - g: "bayaan", - e: "description, statement, speaking, narration, sermon", - r: 4, - c: "n. m.", - app: "بیانات", - apf: "bayaanaat", - } -); -const khufaKedul = vEntry( - { - ts: 1577898920635, - i: 5845, - p: "خفه کېدل", - f: "khufa kedul", - g: "khufakedul", - e: "to become sad, grieved, annoyed, upset; to be choked, to suffocate", - r: 4, - c: "v. stat. comp. intrans.", - l: 1527812798, - ec: "become", - ep: "sad", - }, - { - ts: 1527812798, - i: 5843, - p: "خفه", - f: "khufa", - g: "khufa", - e: "sad, upset, angry; choked, suffocated", - r: 4, - c: "adj.", - } -); -const warkawul = vEntry({ - ts: 1527813914, - i: 14899, - p: "ورکول", - f: "wărkawul", - g: "warkawul", - e: "to give (to him/her/it - towards third person)", - r: 4, - c: "v. trans.", - pprtp: "ورکړی", - pprtf: "wărkúRay", - ec: "give,gives,giving,gave,given", -}); -const raakawul = vEntry({ - ts: 1527819279, - i: 6950, - p: "راکول", - f: "raakawul", - g: "raakawul", - e: "to give (to first person - to me, us)", - r: 4, - c: "v. trans.", - pprtp: "راکړی", - pprtf: "raakúRay", - ec: "give,gives,giving,gave,given", -}); -const darkawul = vEntry({ - ts: 1527817457, - i: 6330, - p: "درکول", - f: "dărkawul", - g: "darkawul", - e: "to give (to second person - you, you pl.)", - r: 4, - pprtp: "درکړی", - pprtf: "dărkúRay", - ec: "give,gives,giving,gave,given", -}); +const raatlul = wordQuery("راتلل", "verb"); +const wartlul = wordQuery("ورتلل", "verb"); +const osedul = wordQuery("اوسېدل", "verb"); +const awuxtul = wordQuery("اوښتل", "verb"); +const khorul = wordQuery("خوړل", "verb"); +const azmoyul = wordQuery("ازمویل", "verb"); +const khatul = wordQuery("ختل", "verb"); +const rasedul = wordQuery("رسېدل", "verb"); +const weshul = wordQuery("وېشل", "verb"); +const watul = wordQuery("وتل", "verb"); +const wurul = wordQuery("وړل", "verb"); +const kexodul = wordQuery("کېښودل", "verb"); +const kenaastul = wordQuery("کېناستل", "verb"); +const ghadzedul = wordQuery("غځېدل", "verb"); +const prexodul = wordQuery("پرېښودل", "verb"); +const raawustul = wordQuery("راوستل", "verb"); +const tlul = wordQuery("تلل", "verb"); +const bandawul = wordQuery("بندول", "verb"); +const bandedul = wordQuery("بندېدل", "verb"); +const sturayKawul = wordQuery("ستړی کول", "verb"); +const sturayKedul = wordQuery("ستړی کېدل", "verb"); +const bayaanedul = wordQuery("بیانېدل", "verb"); +const khufaKedul = wordQuery("خفه کېدل", "verb"); +const warkawul = wordQuery("warkawul", "verb"); +const raakawul = wordQuery("raakawul", "verb"); +const darkawul = wordQuery("درکول", "verb"); const ooPH: T.PH = { type: "PH", ps: { p: "و", f: "óo" } }; @@ -1629,279 +1167,535 @@ describe("perfective roots", () => { }); }); -describe("past participles", () => { - test("for most verbs are just the imperfective root (imperative) plus ی - ay", () => { - expect( - getPastParticiple(rasedul, "active", { - gender: "masc", - number: "singular", - }) - ).toEqual({ - type: "VB", - ps: [{ p: "رسېدلی", f: "rasedúlay" }], - gender: "masc", - number: "singular", - }); - expect( - getPastParticiple(ganul, "active", { gender: "fem", number: "singular" }) - ).toEqual({ - type: "VB", - ps: [{ p: "ګڼلې", f: "gaNúle" }], - gender: "fem", - number: "singular", - }); - }); - test("for verbs like اېښودل and پرېښودل they have a short version shortened taking off after ښ", () => { - expect( - getPastParticiple(prexodul, "active", { - gender: "masc", - number: "plural", - }) - ).toEqual({ - type: "VB", - ps: { - long: [{ p: "پرېښودلي", f: "prexodúlee" }], - short: [{ p: "پرېښي", f: "préxee" }], - }, - gender: "masc", - number: "plural", - }); - }); - test("verbs ending in ستل ښتل وتل or وړل verbs also have a short version", () => { - expect( - getPastParticiple(raawustul, "active", { - gender: "fem", - number: "plural", - }) - ).toEqual({ - type: "VB", - ps: { - long: [{ p: "راوستلې", f: "raawustúle" }], - short: [{ p: "راوستې", f: "raawúste" }], - }, - gender: "fem", - number: "plural", - }); - expect( - getPastParticiple(awuxtul, "active", { gender: "masc", number: "plural" }) - ).toEqual({ - type: "VB", - ps: { - long: [{ p: "اوښتلي", f: "awUxtúlee" }], - short: [{ p: "اوښتي", f: "awÚxtee" }], - }, - gender: "masc", - number: "plural", - }); - expect( - getPastParticiple(watul, "active", { gender: "fem", number: "singular" }) - ).toEqual({ - type: "VB", - ps: { - long: [{ p: "وتلې", f: "watúle" }], - short: [{ p: "وتې", f: "wáte" }], - }, - gender: "fem", - number: "singular", - }); - }); - test("but not verbs ending with استل", () => { - expect( - getPastParticiple(kenaastul, "active", { - gender: "fem", - number: "plural", - }) - ).toEqual({ - type: "VB", - ps: [{ p: "کېناستلې", f: "kenaastúle" }], - gender: "fem", - number: "plural", - }); - }); - test("special short form with تلل - tlul", () => { - expect( - getPastParticiple(tlul, "active", { gender: "masc", number: "plural" }) - ).toEqual({ - type: "VB", - ps: { - long: [{ p: "تللي", f: "tlúlee" }], - short: [{ p: "تلي", f: "túlee" }], - }, - gender: "masc", - number: "plural", - }); - }); - test("kawul/kedul/raatlul verbs have an irregular pprt fields that give us the irregular past participle", () => { - expect( - getPastParticiple(kawulDyn, "active", { - gender: "masc", - number: "singular", - }) - ).toEqual({ - type: "VB", - ps: [{ p: "کړی", f: "kúRay" }], - gender: "masc", - number: "singular", - }); - expect( - getPastParticiple(kawulStat, "active", { - gender: "masc", - number: "plural", - }) - ).toEqual({ - type: "VB", - ps: [{ p: "کړي", f: "kúRee" }], - gender: "masc", - number: "plural", - }); - expect( - getPastParticiple(kedulStat, "active", { - gender: "fem", - number: "singular", - }) - ).toEqual({ - type: "VB", - ps: [{ p: "شوې", f: "shúwe" }], - gender: "fem", - number: "singular", - }); - expect( - getPastParticiple(kedulDyn, "active", { gender: "fem", number: "plural" }) - ).toEqual({ - type: "VB", - ps: [{ p: "شوې", f: "shúwe" }], - gender: "fem", - number: "plural", - }); - }); - test("stative compounds weld the complement to the kawul/kedul participle", () => { - expect( - getPastParticiple(bandawul, "active", { - gender: "fem", - number: "singular", - }) - ).toEqual({ - type: "welded", - left: { - type: "NComp", - comp: { - type: "AdjComp", - ps: { p: "بنده", f: "banda" }, - gender: "fem", - number: "singular", +const pastPartTests: { + label: string; + cases: { + input: [T.VerbEntry, T.Voice, T.GenderNumber]; + output: T.VBP; + }[]; +}[] = [ + { + label: + "for most verbs are just the imperfective root (imperative) plus ی - ay", + cases: [ + { + input: [ + rasedul, + "active", + { + gender: "masc", + number: "singular", + }, + ], + output: { + type: "VB", + ps: [{ p: "رسېدلی", f: "rasedúlay" }], + info: { + type: "ppart", + verb: rasedul, + genNum: { + gender: "masc", + number: "singular", + }, + }, }, }, - right: { - type: "VB", - ps: [{ p: "کړې", f: "kúRe" }], - gender: "fem", - number: "singular", - }, - }); - expect( - getPastParticiple(bandedul, "active", { gender: "fem", number: "plural" }) - ).toEqual({ - type: "welded", - left: { - type: "NComp", - comp: { - type: "AdjComp", - ps: { p: "بندې", f: "bande" }, - gender: "fem", - number: "plural", + { + input: [ + ganul, + "active", + { + gender: "fem", + number: "singular", + }, + ], + output: { + type: "VB", + ps: [{ p: "ګڼلې", f: "gaNúle" }], + info: { + type: "ppart", + verb: ganul, + genNum: { + gender: "fem", + number: "singular", + }, + }, }, }, - right: { - type: "VB", - ps: [{ p: "شوې", f: "shúwe" }], - gender: "fem", - number: "plural", + ], + }, + { + label: + "for verbs like اېښودل and پرېښودل they have a short version shortened taking off after ښ", + cases: [ + { + input: [ + prexodul, + "active", + { + gender: "masc", + number: "plural", + }, + ], + output: { + type: "VB", + ps: { + long: [{ p: "پرېښودلي", f: "prexodúlee" }], + short: [{ p: "پرېښي", f: "préxee" }], + }, + info: { + type: "ppart", + genNum: { + gender: "masc", + number: "plural", + }, + verb: prexodul, + }, + }, }, - }); - }); - test("for passive with simple verbs, long perfective root welded to kedul participle", () => { - expect( - getPastParticiple(ganul, "passive", { gender: "fem", number: "singular" }) - ).toEqual({ - type: "welded", - left: { - type: "VB", - ps: [{ p: "ګڼل", f: "gaNul" }], - }, - right: { - type: "VB", - ps: [{ p: "شوې", f: "shúwe" }], - gender: "fem", - number: "singular", - }, - }); - }); - test("special passive forms for kawul verbs - kRul perfective root + shúway", () => { - expect( - getPastParticiple(kawulStat, "passive", { - gender: "masc", - number: "singular", - }) - ).toEqual({ - type: "welded", - left: { - type: "VB", - ps: [{ p: "کړل", f: "kRul" }], - }, - right: { - type: "VB", - ps: [{ p: "شوی", f: "shúway" }], - gender: "masc", - number: "singular", - }, - }); - expect( - getPastParticiple(kawulDyn, "passive", { - gender: "masc", - number: "singular", - }) - ).toEqual({ - type: "welded", - left: { - type: "VB", - ps: [{ p: "کړل", f: "kRul" }], - }, - right: { - type: "VB", - ps: [{ p: "شوی", f: "shúway" }], - gender: "masc", - number: "singular", - }, - }); - expect( - getPastParticiple(bandawul, "passive", { - gender: "fem", - number: "plural", - }) - ).toEqual({ - type: "welded", - left: { - type: "welded", - left: { - type: "NComp", - comp: { - type: "AdjComp", - ps: { p: "بندې", f: "bande" }, + ], + }, + { + label: "verbs ending in ستل ښتل وتل or وړل verbs also have a short version", + cases: [ + { + input: [ + raawustul, + "active", + { gender: "fem", number: "plural", }, - }, - right: { + ], + output: { type: "VB", - ps: [{ p: "کړل", f: "kRul" }], + ps: { + long: [{ p: "راوستلې", f: "raawustúle" }], + short: [{ p: "راوستې", f: "raawúste" }], + }, + info: { + type: "ppart", + verb: raawustul, + genNum: { + gender: "fem", + number: "plural", + }, + }, }, }, - right: { - type: "VB", - ps: [{ p: "شوې", f: "shúwe" }], - gender: "fem", - number: "plural", + { + input: [awuxtul, "active", { gender: "masc", number: "plural" }], + output: { + type: "VB", + ps: { + long: [{ p: "اوښتلي", f: "awUxtúlee" }], + short: [{ p: "اوښتي", f: "awÚxtee" }], + }, + info: { + type: "ppart", + verb: awuxtul, + genNum: { + gender: "masc", + number: "plural", + }, + }, + }, }, + { + input: [watul, "active", { gender: "fem", number: "singular" }], + output: { + type: "VB", + ps: { + long: [{ p: "وتلې", f: "watúle" }], + short: [{ p: "وتې", f: "wáte" }], + }, + info: { + type: "ppart", + verb: watul, + genNum: { + gender: "fem", + number: "singular", + }, + }, + }, + }, + ], + }, + { + label: "but not verbs ending with استل", + cases: [ + { + input: [ + kenaastul, + "active", + { + gender: "fem", + number: "plural", + }, + ], + output: { + type: "VB", + ps: [{ p: "کېناستلې", f: "kenaastúle" }], + info: { + type: "ppart", + verb: kenaastul, + genNum: { + gender: "fem", + number: "plural", + }, + }, + }, + }, + ], + }, + { + label: "special short form with تلل - tlul", + cases: [ + { + input: [tlul, "active", { gender: "masc", number: "plural" }], + output: { + type: "VB", + ps: { + long: [{ p: "تللي", f: "tlúlee" }], + short: [{ p: "تلي", f: "túlee" }], + }, + info: { + type: "ppart", + verb: tlul, + genNum: { gender: "masc", number: "plural" }, + }, + }, + }, + ], + }, + { + label: + "kawul/kedul/raatlul verbs have an irregular pprt fields that give us the irregular past participle", + cases: [ + { + input: [ + kawulDyn, + "active", + { + gender: "masc", + number: "singular", + }, + ], + output: { + type: "VB", + ps: [{ p: "کړی", f: "kúRay" }], + info: { + type: "ppart", + genNum: { + gender: "masc", + number: "singular", + }, + verb: kawulDyn, + }, + }, + }, + { + input: [ + kawulStat, + "active", + { + gender: "masc", + number: "plural", + }, + ], + output: { + type: "VB", + ps: [{ p: "کړي", f: "kúRee" }], + info: { + type: "ppart", + verb: kawulStat, + genNum: { + gender: "masc", + number: "plural", + }, + }, + }, + }, + { + input: [ + kedulStat, + "active", + { + gender: "fem", + number: "singular", + }, + ], + output: { + type: "VB", + ps: [{ p: "شوې", f: "shúwe" }], + info: { + type: "ppart", + verb: kedulStat, + genNum: { + gender: "fem", + number: "singular", + }, + }, + }, + }, + ], + }, + { + label: + "stative compounds weld the complement to the kawul/kedul participle", + cases: [ + { + input: [ + bandawul, + "active", + { + gender: "fem", + number: "singular", + }, + ], + output: { + type: "welded", + left: { + type: "NComp", + comp: { + type: "AdjComp", + ps: { p: "بنده", f: "banda" }, + gender: "fem", + number: "singular", + }, + }, + right: { + type: "VB", + ps: [{ p: "کړې", f: "kúRe" }], + info: { + type: "ppart", + verb: kawulStat, + genNum: { + gender: "fem", + number: "singular", + }, + }, + }, + info: { + type: "ppart", + verb: bandawul, + genNum: { + gender: "fem", + number: "singular", + }, + }, + }, + }, + { + input: [bandedul, "active", { gender: "fem", number: "plural" }], + output: { + type: "welded", + left: { + type: "NComp", + comp: { + type: "AdjComp", + ps: { p: "بندې", f: "bande" }, + gender: "fem", + number: "plural", + }, + }, + right: { + type: "VB", + ps: [{ p: "شوې", f: "shúwe" }], + info: { + type: "ppart", + verb: kedulStat, + genNum: { + gender: "fem", + number: "plural", + }, + }, + }, + info: { + type: "ppart", + verb: bandedul, + genNum: { + gender: "fem", + number: "plural", + }, + }, + }, + }, + ], + }, + { + label: + "for passive with simple verbs, long perfective root welded to kedul participle", + cases: [ + { + input: [ganul, "passive", { gender: "fem", number: "singular" }], + output: { + type: "welded", + left: { + type: "VB", + ps: [{ p: "ګڼل", f: "gaNul" }], + }, + right: { + type: "VB", + ps: [{ p: "شوې", f: "shúwe" }], + info: { + type: "ppart", + verb: kedulStat, + genNum: { + gender: "fem", + number: "singular", + }, + }, + }, + info: { + type: "ppart", + verb: ganul, + genNum: { + gender: "fem", + number: "singular", + }, + }, + }, + }, + ], + }, + { + label: + "special passive forms for kawul verbs - kRul perfective root + shúway", + cases: [ + { + input: [ + kawulStat, + "passive", + { + gender: "masc", + number: "singular", + }, + ], + output: { + type: "welded", + left: { + type: "VB", + ps: [{ p: "کړل", f: "kRul" }], + }, + right: { + type: "VB", + ps: [{ p: "شوی", f: "shúway" }], + info: { + type: "ppart", + verb: kedulStat, + genNum: { + gender: "masc", + number: "singular", + }, + }, + }, + info: { + type: "ppart", + verb: kawulStat, + genNum: { + gender: "masc", + number: "singular", + }, + }, + }, + }, + { + input: [ + kawulDyn, + "passive", + { + gender: "masc", + number: "singular", + }, + ], + output: { + type: "welded", + left: { + type: "VB", + ps: [{ p: "کړل", f: "kRul" }], + }, + right: { + type: "VB", + ps: [{ p: "شوی", f: "shúway" }], + info: { + type: "ppart", + verb: kedulStat, + genNum: { + gender: "masc", + number: "singular", + }, + }, + }, + info: { + type: "ppart", + verb: kawulDyn, + genNum: { + gender: "masc", + number: "singular", + }, + }, + }, + }, + { + input: [ + bandawul, + "passive", + { + gender: "fem", + number: "plural", + }, + ], + output: { + type: "welded", + left: { + type: "welded", + left: { + type: "NComp", + comp: { + type: "AdjComp", + ps: { p: "بندې", f: "bande" }, + gender: "fem", + number: "plural", + }, + }, + right: { + type: "VB", + ps: [{ p: "کړل", f: "kRul" }], + }, + }, + right: { + type: "VB", + ps: [{ p: "شوې", f: "shúwe" }], + info: { + type: "ppart", + verb: kedulStat, + genNum: { + gender: "fem", + number: "plural", + }, + }, + }, + info: { + type: "ppart", + verb: bandawul, + genNum: { + gender: "fem", + number: "plural", + }, + }, + }, + }, + ], + }, +]; + +describe("past participles", () => { + pastPartTests.forEach(({ label, cases }) => { + test(label, () => { + cases.forEach(({ input, output }) => { + expect(getPastParticiple(...input)).toEqual(output); + }); }); }); }); @@ -1942,6 +1736,11 @@ describe("ability roots and stems", () => { { p: "ختای", f: "khatáay" }, ], }, + info: { + type: "ability", + verb: khatul, + aspect: "imperfective", + }, }, { type: "VB", @@ -1970,6 +1769,11 @@ describe("ability roots and stems", () => { { p: "ختای", f: "khataay" }, ], }, + info: { + type: "ability", + verb: khatul, + aspect: "perfective", + }, }, { type: "VB", @@ -1998,6 +1802,11 @@ describe("ability roots and stems", () => { { p: "ختای", f: "khatáay" }, ], }, + info: { + type: "ability", + verb: khatul, + aspect: "imperfective", + }, }, { type: "VB", @@ -2029,6 +1838,11 @@ describe("ability roots and stems", () => { { p: "ختای", f: "khataay" }, ], }, + info: { + type: "ability", + verb: khatul, + aspect: "perfective", + }, }, { type: "VB", @@ -2043,7 +1857,8 @@ describe("ability roots and stems", () => { ], }, { - title: "tlul verbs lose the perfective aspect", + title: + "tlul verbs and verbs with irregular perfective roots lose the perfective aspect", tests: [ { verb: raatlul, @@ -2065,6 +1880,11 @@ describe("ability roots and stems", () => { { p: "راتلای", f: "raatláay" }, ], }, + info: { + type: "ability", + verb: raatlul, + aspect: "imperfective", + }, }, { type: "VB", @@ -2093,6 +1913,77 @@ describe("ability roots and stems", () => { { p: "راتلای", f: "raatláay" }, ], }, + info: { + type: "ability", + verb: raatlul, + aspect: "imperfective", + }, + }, + { + type: "VB", + ps: [{ p: "ش", f: "sh" }], + }, + ], + ], + }, + { + verb: wurul, + aspect: "perfective", + rs: "stem", + voice: "active", + result: [ + [], + [ + { + type: "VB", + ps: { + long: [ + { p: "وړلی", f: "wuRúlay" }, + { p: "وړلای", f: "wuRúlaay" }, + ], + short: [ + { p: "وړی", f: "wuRáy" }, + { p: "وړای", f: "wuRáay" }, + ], + }, + info: { + type: "ability", + verb: wurul, + aspect: "imperfective", + }, + }, + { + type: "VB", + ps: [{ p: "ش", f: "sh" }], + }, + ], + ], + }, + { + verb: wurul, + aspect: "imperfective", + rs: "stem", + voice: "active", + result: [ + [], + [ + { + type: "VB", + ps: { + long: [ + { p: "وړلی", f: "wuRúlay" }, + { p: "وړلای", f: "wuRúlaay" }, + ], + short: [ + { p: "وړی", f: "wuRáy" }, + { p: "وړای", f: "wuRáay" }, + ], + }, + info: { + type: "ability", + verb: wurul, + aspect: "imperfective", + }, }, { type: "VB", @@ -2126,6 +2017,11 @@ describe("ability roots and stems", () => { { p: "بندېدای", f: "bandedáay" }, ], }, + info: { + type: "ability", + verb: bandedul, + aspect: "imperfective", + }, }, { type: "VB", @@ -2167,6 +2063,11 @@ describe("ability roots and stems", () => { ], }, }, + info: { + type: "ability", + verb: achawul, + aspect: "perfective", + }, }, { type: "VB", diff --git a/src/lib/src/new-verb-engine/roots-and-stems.ts b/src/lib/src/new-verb-engine/roots-and-stems.ts index 3781709..5bd1c59 100644 --- a/src/lib/src/new-verb-engine/roots-and-stems.ts +++ b/src/lib/src/new-verb-engine/roots-and-stems.ts @@ -15,7 +15,7 @@ import { countSyllables, removeAccents, } from "../accent-helpers"; -import { isKawulVerb, isTlulVerb } from "../type-predicates"; +import { isKawulVerb } from "../type-predicates"; import { vEntry, addAbilityEnding, @@ -123,42 +123,60 @@ function getAbilityRs( rs: "root" | "stem", voice: T.Voice, genderNum: T.GenderNumber -): [[] | [T.VHead], [T.VB, T.VBA]] { +): [[] | [T.VHead], [T.VBP, T.VB]] { + // https://grammar.lingdocs.com/verbs/ability/#exceptions const losesAspect = - isTlulVerb(verb) || + (verb.entry.prp && verb.entry.p !== "کول") || (isStatComp(verb) && vTransitivity(verb) === "intransitive"); + const asp = losesAspect ? "imperfective" : aspect; const [vhead, [basicroot]] = voice === "passive" ? getPassiveRs(verb, "imperfective", "root", genderNum) - : getRoot(verb, genderNum, losesAspect ? "imperfective" : aspect); - return [vhead, [addAbilityEnding(basicroot), rs === "root" ? shwulVB : shVB]]; + : getRoot(verb, genderNum, asp); + return [ + vhead, + [addAbilityEnding(basicroot, verb, asp), rs === "root" ? shwulVB : shVB], + ]; } export function getPastParticiple( verb: T.VerbEntry, voice: T.Voice, { gender, number }: { gender: T.Gender; number: T.NounNumber } -): T.VBGenNum | T.WeldedGN { +): T.VBP { const v = removeFVarientsFromVerb(verb); if (voice === "passive") { return getPassivePp(v, { gender, number }); } if (isStatComp(v) && v.complement) { - return weld( - makeComplement(v.complement, { gender, number }), - getPastParticiple(statVerb[vTransitivity(verb)], voice, { - gender, - number, - }) as T.VBGenNum - ); + return { + ...weld( + makeComplement(v.complement, { gender, number }), + getPastParticiple(statVerb[vTransitivity(verb)], voice, { + gender, + number, + }) + ), + info: { + type: "ppart", + genNum: { gender, number }, + verb, + }, + }; } if (verb.entry.pprtp && verb.entry.pprtf) { const base = makePsString(verb.entry.pprtp, verb.entry.pprtf); return { type: "VB", ps: inflectPattern3(base, { gender, number }), - gender, - number, + info: { + type: "ppart", + verb, + genNum: { + gender, + number, + }, + }, }; } const basicRoot = getRoot( @@ -166,7 +184,7 @@ export function getPastParticiple( { gender, number }, "imperfective" )[1][0]; - const longRoot = getLongVB(basicRoot); + const longRoot = getLongVB(basicRoot) as T.VBNoLenghts; const rootWLengths = possiblePPartLengths(longRoot); /* istanbul ignore next */ if ("right" in rootWLengths) { @@ -175,8 +193,14 @@ export function getPastParticiple( return { ...rootWLengths, ps: addTail(rootWLengths.ps), - gender, - number, + info: { + type: "ppart", + verb, + genNum: { + gender, + number, + }, + }, }; function addTail( @@ -192,12 +216,19 @@ export function getPastParticiple( function getPassivePp( verb: T.VerbEntryNoFVars, genderNumber: T.GenderNumber -): T.WeldedGN { +): T.VBP { if (isStatComp(verb) && verb.complement) { - return weld( - makeComplement(verb.complement, genderNumber), - getPassivePp(statVerb.transitive, genderNumber) - ); + return { + ...weld( + makeComplement(verb.complement, genderNumber), + getPassivePp(statVerb.transitive, genderNumber) + ), + info: { + type: "ppart", + verb, + genNum: genderNumber, + }, + }; } const basicRoot = getRoot( verb, @@ -205,38 +236,26 @@ function getPassivePp( isKawulVerb(verb) ? "perfective" : "imperfective" )[1][0]; const longRoot = getLongVB(basicRoot); - const kedulVb: T.VBGenNum = getPastParticiple( + const kedulVb = getPastParticiple( statVerb.intransitive, "active", genderNumber - ) as T.VBGenNum; - return weld(longRoot, kedulVb); -} - -function getPassiveRs( - verb: T.VerbEntryNoFVars, - aspect: T.Aspect, - rs: "root" | "stem", - genderNumber: T.GenderNumber -): [[] | [T.VHead], [T.VBA]] { - const [vHead, [basicRoot]] = getRoot(verb, genderNumber, aspect); - const longRoot = getLongVB(basicRoot); - const kedulVba = getRootStem({ - verb: statVerb.intransitive, - aspect, - rs, - type: "basic", - voice: "active", - genderNumber: { gender: "masc", number: "singular" }, - })[1][0] as T.VBBasic; - return [vHead, [weld(longRoot, kedulVba)]]; + ); + return { + ...weld(longRoot, kedulVb), + info: { + type: "ppart", + verb, + genNum: genderNumber, + }, + }; } function getRoot( verb: T.VerbEntryNoFVars, genderNum: T.GenderNumber, aspect: T.Aspect -): [[T.VHead] | [], [T.VBA]] { +): [[T.VHead] | [], [T.VB]] { if ( verb.complement && isStatComp(verb) && @@ -430,6 +449,25 @@ function getStem( } } +function getPassiveRs( + verb: T.VerbEntryNoFVars, + aspect: T.Aspect, + rs: "root" | "stem", + genderNumber: T.GenderNumber +): [[] | [T.VHead], [T.VB]] { + const [vHead, [basicRoot]] = getRoot(verb, genderNumber, aspect); + const longRoot = getLongVB(basicRoot); + const kedulVba = getRootStem({ + verb: statVerb.intransitive, + aspect, + rs, + type: "basic", + voice: "active", + genderNumber: { gender: "masc", number: "singular" }, + })[1][0] as T.VBBasic; + return [vHead, [weld(longRoot, kedulVba)]]; +} + // TODO: This is a nasty and messy way to do it with the length options included function getPerfectiveHead( base: T.PsString, diff --git a/src/lib/src/new-verb-engine/rs-helpers.ts b/src/lib/src/new-verb-engine/rs-helpers.ts index 480716a..d6d433b 100644 --- a/src/lib/src/new-verb-engine/rs-helpers.ts +++ b/src/lib/src/new-verb-engine/rs-helpers.ts @@ -123,19 +123,10 @@ export function verbEndingConcat( ); } -// TODO: THIS IS UGGGGLY NEED TO THINK THROUGH THE TYPING ON THE WELDING export function weld( left: T.Welded["left"], - right: T.VBGenNum | T.WeldedGN -): T.WeldedGN; -export function weld( - left: T.Welded["left"], - right: T.VBBasic | T.NComp | T.Welded -): T.Welded; -export function weld( - left: T.Welded["left"], - right: T.VBBasic | T.VBGenNum | T.Welded | T.NComp | T.WeldedGN -): T.Welded | T.WeldedGN { + right: T.VB | T.VBP | T.NComp +): T.Welded { if (right.type === "welded") { return weld(weld(left, right.left), right.right); } @@ -218,7 +209,11 @@ export function tlulPerfectiveStem(person: { ]; } -export function addAbilityEnding(vb: T.VBA): T.VBA { +export function addAbilityEnding( + vb: T.VB, + verb: T.VerbEntry, + aspect: T.Aspect +): T.VBP { const abilityEnding: T.PsString[] = [ { p: "ی", f: "ay" }, { p: "ای", f: "aay" }, @@ -227,9 +222,21 @@ export function addAbilityEnding(vb: T.VBA): T.VBA { return { ...vb, right: addToEnd(vb.right, abilityEnding), + info: { + type: "ability", + verb, + aspect, + }, }; } - return addToEnd(vb, abilityEnding); + return { + ...addToEnd(vb, abilityEnding), + info: { + type: "ability", + verb, + aspect, + }, + }; function addToEnd(vb: T.VBBasic, end: T.PsString[]): T.VBBasic { /* istanbul ignore next */ if (!("long" in vb.ps)) { @@ -248,8 +255,8 @@ export function addAbilityEnding(vb: T.VBA): T.VBA { } export function possiblePPartLengths(vba: T.VBNoLenghts): T.VBBasic; -export function possiblePPartLengths(vba: T.VBNoLenghts): T.VBA; -export function possiblePPartLengths(vba: T.VBNoLenghts): T.VBA { +export function possiblePPartLengths(vba: T.VBNoLenghts): T.VB; +export function possiblePPartLengths(vba: T.VBNoLenghts): T.VB { const shortenableEndings = ["ښتل", "ستل", "وتل"]; const wrul = ["وړل", "راوړل", "وروړل", "دروړل"]; // can't find a case where this is used - type safety @@ -294,12 +301,11 @@ export function possiblePPartLengths(vba: T.VBNoLenghts): T.VBA { return vba; } -export function getLongVB(vb: T.VBBasic): T.VBNoLenghts; -export function getLongVB(vb: T.VBA): T.VBNoLenghts; -export function getLongVB(vb: T.VBA): T.VBNoLenghts { +export function getLongVB(vb: T.VB): T.VBNoLenghts { if (vb.type === "welded") { return { ...vb, + // @ts-ignore right: getLongVB(vb.right), }; } diff --git a/src/lib/src/parsing/inflection-query.ts b/src/lib/src/parsing/inflection-query.ts index 1807ad9..a443f0d 100644 --- a/src/lib/src/parsing/inflection-query.ts +++ b/src/lib/src/parsing/inflection-query.ts @@ -1,4 +1,5 @@ import * as T from "../../../types"; +import { endsInConsonant } from "../p-text-helpers"; import { isPattern1Entry, isPattern2Entry, @@ -50,6 +51,7 @@ export function getInflectionQueries( }, }); if (noun) { + // TODO: could merge these queries for more efficiency ?? queries.push({ search: { ppp: s }, details: { @@ -59,7 +61,17 @@ export function getInflectionQueries( predicate: isNounEntry, }, }); - if (s.endsWith("و")) { + queries.push({ + search: { app: s }, + details: { + inflection: [0], + gender: ["masc", "fem"], + plural: true, + predicate: isNounEntry, + }, + }); + // TODO: what about short vowel ending nouns with وو etc + if (s.endsWith("و") && !["ا", "و"].includes(s.charAt(s.length - 2))) { queries.push({ search: { ppp: s.slice(0, -1) }, details: { @@ -69,6 +81,15 @@ export function getInflectionQueries( predicate: isMascNounEntry, }, }); + queries.push({ + search: { app: s.slice(0, -1) }, + details: { + inflection: [1], + gender: ["masc"], + plural: true, + predicate: isMascNounEntry, + }, + }); queries.push({ search: { ppp: s.slice(0, -1) + "ې" }, details: { @@ -218,6 +239,15 @@ export function getInflectionQueries( !isPattern4Entry(e), }, }); + queries.push({ + search: { app: s.slice(0, -2) }, + details: { + inflection: [1], + gender: ["masc"], + plural: true, + predicate: (e) => isNounEntry(e), + }, + }); } if ( s.endsWith("ګانو") && @@ -364,6 +394,18 @@ export function getInflectionQueries( predicate: isPattern1Entry, }, }); + if (noun) { + // bundled plural + queries.push({ + search: { p: s.slice(0, -1) }, + details: { + inflection: [0], + plural: true, + gender: ["masc"], + predicate: (e) => !isPattern5Entry(e) && endsInConsonant(e), + }, + }); + } queries.push({ search: { infbp: s.slice(0, -1) }, details: { diff --git a/src/lib/src/parsing/lookup.tsx b/src/lib/src/parsing/lookup.tsx index c109686..b8b1d96 100644 --- a/src/lib/src/parsing/lookup.tsx +++ b/src/lib/src/parsing/lookup.tsx @@ -1,9 +1,12 @@ import nounsAdjs from "../../../nouns-adjs"; +import verbs from "../../../verbs"; import * as T from "../../../types"; import { isAdjectiveEntry, isNounEntry } from "../type-predicates"; +import { removeFVarientsFromVerb } from "../accent-and-ps-utils"; export function lookup(s: Partial): T.DictionaryEntry[] { const [key, value] = Object.entries(s)[0]; + // TODO: could make this more efficient - merging ppp and app queries? if (key === "ppp") { return nounsAdjs.filter( (e) => @@ -14,16 +17,42 @@ export function lookup(s: Partial): T.DictionaryEntry[] { .includes(value as string) ); } + if (key === "ppp") { + return nounsAdjs.filter( + (e) => + e.app && + e.app + .split(",") + .map((w) => w.trim()) + .includes(value as string) + ); + } // @ts-ignore return nounsAdjs.filter((e) => e[key] === value) as T.DictionaryEntry[]; } +export function verbLookup( + s: (e: T.VerbDictionaryEntry) => boolean +): T.VerbEntry[] { + return verbs.filter(({ entry }) => s(entry)); +} + export function wordQuery(word: string, type: "adj"): T.AdjectiveEntry; export function wordQuery(word: string, type: "noun"): T.NounEntry; +export function wordQuery(word: string, type: "verb"): T.VerbEntryNoFVars; export function wordQuery( word: string, - type: "noun" | "adj" -): T.NounEntry | T.AdjectiveEntry { + type: "noun" | "adj" | "verb" +): T.NounEntry | T.AdjectiveEntry | T.VerbEntryNoFVars { + if (type === "verb") { + const verb = verbs.find( + (x) => x.entry.p === word || x.entry.f === word || x.entry.g === word + ); + if (!verb) { + throw new Error(`missing ${word} in word query`); + } + return removeFVarientsFromVerb(verb); + } const entry = nounsAdjs.find( (x) => x.p === word || x.f === word || x.g === word ); diff --git a/src/lib/src/parsing/parse-adjective.test.ts b/src/lib/src/parsing/parse-adjective.test.ts index 71e5cae..8afff0e 100644 --- a/src/lib/src/parsing/parse-adjective.test.ts +++ b/src/lib/src/parsing/parse-adjective.test.ts @@ -1,67 +1,14 @@ import { makeAdjectiveSelection } from "../phrase-building/make-selections"; import * as T from "../../../types"; -import { lookup } from "./lookup"; +import { lookup, wordQuery } from "./lookup"; import { parseAdjective } from "./parse-adjective"; import { tokenizer } from "./tokenizer"; -const ghut = { - ts: 1527812625, - i: 9561, - p: "غټ", - f: "ghuT, ghaT", - g: "ghuT,ghaT", - e: "big, fat", - r: 4, - c: "adj.", -} as T.AdjectiveEntry; -const sturey = { - ts: 1527815306, - i: 7933, - p: "ستړی", - f: "stúRay", - g: "stuRay", - e: "tired", - r: 4, - c: "adj. / adv.", -} as T.AdjectiveEntry; -const narey = { - ts: 1527819320, - i: 14027, - p: "نری", - f: "naráy", - g: "naray", - e: "thin; mild; high (pitch)", - r: 4, - c: "adj.", -} as T.AdjectiveEntry; -const zor = { - ts: 1527815451, - i: 7570, - p: "زوړ", - f: "zoR", - g: "zoR", - e: "old", - r: 4, - c: "adj.", - infap: "زاړه", - infaf: "zaaRu", - infbp: "زړ", - infbf: "zaR", -} as T.AdjectiveEntry; -const sheen = { - ts: 1527815265, - i: 8979, - p: "شین", - f: "sheen", - g: "sheen", - e: "green, blue; unripe, immature; bright, sunny", - r: 4, - c: "adj.", - infap: "شنه", - infaf: "shnu", - infbp: "شن", - infbf: "shn", -} as T.AdjectiveEntry; +const ghut = wordQuery("غټ", "adj"); +const sturey = wordQuery("ستړی", "adj"); +const narey = wordQuery("نری", "adj"); +const zor = wordQuery("زوړ", "adj"); +const sheen = wordQuery("شین", "adj"); const tests: { category: string; @@ -312,7 +259,7 @@ describe("parsing adjectives", () => { test(category, () => { cases.forEach(({ input, output }) => { const tokens = tokenizer(input); - const possibilities = parseAdjective(tokens, lookup).map((x) => x[1]); + const possibilities = parseAdjective(tokens, lookup).map((x) => x.body); expect( possibilities.map((x) => { const { given, ...rest } = x; diff --git a/src/lib/src/parsing/parse-noun.test.ts b/src/lib/src/parsing/parse-noun.test.ts index b6bf9ff..dbc5e60 100644 --- a/src/lib/src/parsing/parse-noun.test.ts +++ b/src/lib/src/parsing/parse-noun.test.ts @@ -6,6 +6,7 @@ import * as T from "../../../types"; import { lookup, wordQuery } from "./lookup"; import { parseNoun } from "./parse-noun"; import { tokenizer } from "./tokenizer"; +import { isCompleteResult } from "./utils"; const sor = wordQuery("سوړ", "adj"); const zor = wordQuery("زوړ", "adj"); @@ -36,6 +37,12 @@ const maamaa = wordQuery("ماما", "noun"); const peesho = wordQuery("پیشو", "noun"); const duaa = wordQuery("دعا", "noun"); const zooy = wordQuery("زوی", "noun"); +const nabee = wordQuery("نبي", "noun"); +const lafz = wordQuery("لفظ", "noun"); + +// TODO: test for adjective errors etc + +// bundled plural const tests: { category: string; @@ -123,6 +130,13 @@ const tests: { gender: "fem", }, }, + { + inflected: false, + selection: { + ...makeNounSelection(daktar, undefined), + number: "plural", + }, + }, ], }, { @@ -1290,18 +1304,98 @@ const tests: { }, ], }, + { + category: "arabic plurals", + cases: [ + { + input: "الفاظ", + output: [ + { + inflected: false, + selection: { + ...makeNounSelection(lafz, undefined), + number: "plural", + }, + }, + ], + }, + { + input: "الفاظو", + output: [ + { + inflected: true, + selection: { + ...makeNounSelection(lafz, undefined), + number: "plural", + }, + }, + ], + }, + { + input: "نبي", + output: [ + { + inflected: false, + selection: makeNounSelection(nabee, undefined), + }, + { + inflected: true, + selection: makeNounSelection(nabee, undefined), + }, + ], + }, + { + input: "انبیا", + output: [ + { + inflected: false, + selection: { + ...makeNounSelection(nabee, undefined), + number: "plural", + }, + }, + ], + }, + { + input: "انبیاوو", + output: [ + { + inflected: true, + selection: { + ...makeNounSelection(nabee, undefined), + number: "plural", + }, + }, + ], + }, + ], + }, + { + category: "bundled plurals", + cases: [ + { + input: "کوره", + output: [ + { + inflected: false, + selection: { + ...makeNounSelection(kor, undefined), + number: "plural", + }, + }, + ], + }, + ], + }, ]; -// PROBLEM WITH غټې وریژې -// ]; - describe("parsing nouns", () => { tests.forEach(({ category, cases }) => { // eslint-disable-next-line jest/valid-title test(category, () => { cases.forEach(({ input, output }) => { const tokens = tokenizer(input); - const res = parseNoun(tokens, lookup, undefined).map(([t, res]) => res); + const res = parseNoun(tokens, lookup).map(({ body }) => body); expect(res).toEqual(output); }); }); @@ -1407,10 +1501,8 @@ const adjsTests: { }, ], }, - // TODO: testing issue with the parser returning multiple options needs - // to be worked out to test double adjectives { - input: "غټو کورونو", + input: "غټو زړو کورونو", output: [ { inflected: true, @@ -1419,7 +1511,7 @@ const adjsTests: { number: "plural", adjectives: [ makeAdjectiveSelection(ghut), - // makeAdjectiveSelection(zor), + makeAdjectiveSelection(zor), ], }, }, @@ -1429,15 +1521,17 @@ const adjsTests: { }, ]; -// describe("parsing nouns with adjectives", () => { -// adjsTests.forEach(({ category, cases }) => { -// // eslint-disable-next-line jest/valid-title -// test(category, () => { -// cases.forEach(({ input, output }) => { -// const tokens = tokenizer(input); -// const res = parseNoun(tokens, lookup, undefined).map(([t, res]) => res); -// expect(res).toEqual(output); -// }); -// }); -// }); -// }); +describe("parsing nouns with adjectives", () => { + adjsTests.forEach(({ category, cases }) => { + // eslint-disable-next-line jest/valid-title + test(category, () => { + cases.forEach(({ input, output }) => { + const tokens = tokenizer(input); + const res = parseNoun(tokens, lookup) + .filter(isCompleteResult) + .map(({ body }) => body); + expect(res).toEqual(output); + }); + }); + }); +}); diff --git a/src/lib/src/parsing/parse-noun.ts b/src/lib/src/parsing/parse-noun.ts index 6008492..cd2f1c9 100644 --- a/src/lib/src/parsing/parse-noun.ts +++ b/src/lib/src/parsing/parse-noun.ts @@ -9,102 +9,31 @@ import { } from "../type-predicates"; import { getInflectionQueries } from "./inflection-query"; import { parseAdjective } from "./parse-adjective"; -import { groupWith, equals } from "rambda"; +import { parsePossesor } from "./parse-possesor"; +import { bindParseResult } from "./utils"; -// TODO: -// - cleanup the workflow and make sure all nouns are covered and test -// - add possesive parsing type NounResult = { inflected: boolean; selection: T.NounSelection }; export function parseNoun( tokens: Readonly, - lookup: (s: Partial) => T.DictionaryEntry[], - prevPossesor: { inflected: boolean; selection: T.NounSelection } | undefined + lookup: (s: Partial) => T.DictionaryEntry[] ): T.ParseResult[] { if (tokens.length === 0) { return []; } - const [first, ...rest] = tokens; - const possesor = - first.s === "د" ? parseNoun(rest, lookup, undefined) : undefined; - if (possesor) { - const runsAfterPossesor: T.ParseResult[] = possesor - ? possesor - : [{ tokens, body: undefined, errors: [] }]; - // could be a case for a monad ?? - return removeUnneccesaryFailing( - runsAfterPossesor.flatMap( - ({ tokens, body: possesor, errors }) => - parseNoun( - tokens, - lookup, - possesor - ? { - inflected: possesor.inflected, - selection: { - ...possesor.selection, - possesor: prevPossesor - ? { - shrunken: false, - np: { - type: "NP", - selection: prevPossesor.selection, - }, - } - : undefined, - }, - } - : undefined - ) - // .map>(([t, r, errs]) => [ - // t, - // r, - // // TODO: should the errors from the runsAfterPossesor be thrown out? - // // or ...errors should be kept? - // // to show an error like د غتو ماشومان نومونه - // // adj error غټ should be first inflection (seems confusing) - // [...errs, ...errors], - // ]) - ) - ); - } else { - return removeUnneccesaryFailing( - parseNounAfterPossesor(tokens, lookup, prevPossesor, []) - ); + const possesor = parsePossesor(tokens, lookup, undefined); + if (possesor.length) { + return bindParseResult(possesor, (tokens, p) => { + return parseNounAfterPossesor(tokens, lookup, p, []); + }); } + return parseNounAfterPossesor(tokens, lookup, undefined, []); } -function removeUnneccesaryFailing( - results: T.ParseResult[] -): T.ParseResult[] { - // group by identical results - const groups = groupWith( - (a, b) => equals(a.body.selection, b.body.selection), - results - ); - // if there's a group of identical results with some success in it - // remove any erroneous results - const stage1 = groups.flatMap((group) => { - if (group.find((x) => x.errors.length === 0)) { - return group.filter((x) => x.errors.length === 0); - } - return group; - }); - // finally, if there's any success anywhere, remove any of the errors - if (stage1.find((x) => x.errors.length === 0)) { - return stage1.filter((x) => x.errors.length === 0); - } else { - return stage1; - } -} - -// create NP parsing function for that -// TODO with possesor, parse an NP not a noun - function parseNounAfterPossesor( tokens: Readonly, lookup: (s: Partial) => T.DictionaryEntry[], - possesor: { inflected: boolean; selection: T.NounSelection } | undefined, + possesor: T.PossesorSelection | undefined, adjectives: { inflection: (0 | 1 | 2)[]; gender: T.Gender[]; @@ -117,14 +46,13 @@ function parseNounAfterPossesor( } // TODO: add recognition of او between adjectives const adjRes = parseAdjective(tokens, lookup); - const withAdj = adjRes.flatMap(({ tokens: tkns, body: adj }) => + const withAdj = bindParseResult(adjRes, (tkns, adj) => parseNounAfterPossesor(tkns, lookup, possesor, [...adjectives, adj]) ); const [first, ...rest] = tokens; - const w: ReturnType = []; - const searches = getInflectionQueries(first.s, true); + const w: ReturnType = []; searches.forEach(({ search, details }) => { const nounEntries = lookup(search).filter(isNounEntry); details.forEach((deets) => { @@ -147,6 +75,11 @@ function parseNounAfterPossesor( convertInflection(inf, entry, gender, deets.plural).forEach( ({ inflected, number }) => { const selection = makeNounSelection(entry, undefined); + const errors = [ + ...adjErrors.map((message) => ({ + message, + })), + ]; w.push({ tokens: rest, body: { @@ -162,25 +95,10 @@ function parseNounAfterPossesor( adjectives: adjectives.map((a) => a.selection), // TODO: could be nicer to validate that the possesor is inflected before // and just pass in the selection - possesor: possesor - ? { - shrunken: false, - np: { - type: "NP", - selection: possesor.selection, - }, - } - : undefined, + possesor, }, }, - errors: [ - ...(possesor?.inflected === false - ? [{ message: "possesor should be inflected" }] - : []), - ...adjErrors.map((message) => ({ - message, - })), - ], + errors, }); } ); diff --git a/src/lib/src/parsing/parse-np.ts b/src/lib/src/parsing/parse-np.ts index 8730dbf..a9097e3 100644 --- a/src/lib/src/parsing/parse-np.ts +++ b/src/lib/src/parsing/parse-np.ts @@ -4,9 +4,13 @@ import { parseNoun } from "./parse-noun"; import { fmapParseResult } from "../fp-ps"; export function parseNP( - s: T.Token[], + s: Readonly, lookup: (s: Partial) => T.DictionaryEntry[] ): T.ParseResult<{ inflected: boolean; selection: T.NPSelection }>[] { + if (s.length === 0) { + return []; + } + function makeNPSl( a: | { @@ -33,6 +37,6 @@ export function parseNP( // @ts-ignore grrr webpack is having trouble with this return fmapParseResult(makeNPSl, [ ...parsePronoun(s), - ...parseNoun(s, lookup, undefined), + ...parseNoun(s, lookup), ]); } diff --git a/src/lib/src/parsing/parse-phrase.ts b/src/lib/src/parsing/parse-phrase.ts index cbf5d3b..be1e9f8 100644 --- a/src/lib/src/parsing/parse-phrase.ts +++ b/src/lib/src/parsing/parse-phrase.ts @@ -1,20 +1,34 @@ import * as T from "../../../types"; +import { verbLookup } from "./lookup"; import { parseNP } from "./parse-np"; +import { parseVerb } from "./parse-verb"; +import { parseVP } from "./parse-vp"; export function parsePhrase( s: T.Token[], lookup: (s: Partial) => T.DictionaryEntry[] ): { - success: { inflected: boolean; selection: T.NPSelection }[]; + success: ( + | { + inflected: boolean; + selection: T.NPSelection; + } + | Omit + | T.VPSelectionComplete + )[]; errors: string[]; } { - const nps = parseNP(s, lookup).filter(({ tokens }) => !tokens.length); + const res = [ + ...parseNP(s, lookup).filter(({ tokens }) => !tokens.length), + ...parseVerb(s, verbLookup), + ...parseVP(s, lookup, verbLookup), + ]; - const success = nps.map((x) => x.body); + const success = res.map((x) => x.body); return { success, errors: [ - ...new Set(nps.flatMap(({ errors }) => errors.map((e) => e.message))), + ...new Set(res.flatMap(({ errors }) => errors.map((e) => e.message))), ], }; } diff --git a/src/lib/src/parsing/parse-possesor.test.ts b/src/lib/src/parsing/parse-possesor.test.ts new file mode 100644 index 0000000..b0b644c --- /dev/null +++ b/src/lib/src/parsing/parse-possesor.test.ts @@ -0,0 +1,124 @@ +/* eslint-disable jest/no-conditional-expect */ +import * as T from "../../../types"; +import { + makeAdjectiveSelection, + makeNounSelection, + makePronounSelection, +} from "../phrase-building/make-selections"; +import { lookup, wordQuery } from "./lookup"; +import { parsePossesor } from "./parse-possesor"; +import { tokenizer } from "./tokenizer"; +import { isCompleteResult } from "./utils"; + +const sturey = wordQuery("ستړی", "adj"); +const sarey = wordQuery("سړی", "noun"); +const maashoom = wordQuery("ماشوم", "noun"); +const malguray = wordQuery("ملګری", "noun"); +const plaar = wordQuery("پلار", "noun"); + +const tests: { + input: string; + output: T.NPSelection["selection"][] | "error"; +}[] = [ + { + input: "د سړي", + output: [makeNounSelection(sarey, undefined)], + }, + { + input: "د ماشومې", + output: [ + { + ...makeNounSelection(maashoom, undefined), + gender: "fem", + }, + ], + }, + { + input: "د ستړي پلار د ملګري", + output: [ + { + ...makeNounSelection(malguray, undefined), + possesor: { + shrunken: false, + np: { + type: "NP", + selection: { + ...makeNounSelection(plaar, undefined), + adjectives: [makeAdjectiveSelection(sturey)], + }, + }, + }, + }, + ], + }, + { + input: "د سړی نوم", + output: "error", + }, + { + input: "د ښځې د ماشومه", + output: "error", + }, + { + input: "زما", + output: [ + makePronounSelection(T.Person.FirstSingMale), + makePronounSelection(T.Person.FirstSingFemale), + ], + }, + { + input: "ستا", + output: [ + makePronounSelection(T.Person.SecondSingMale), + makePronounSelection(T.Person.SecondSingFemale), + ], + }, + { + input: "زمونږ", + output: [ + makePronounSelection(T.Person.FirstPlurMale), + makePronounSelection(T.Person.FirstPlurFemale), + ], + }, + { + input: "زموږ", + output: [ + makePronounSelection(T.Person.FirstPlurMale), + makePronounSelection(T.Person.FirstPlurFemale), + ], + }, + { + input: "ستاسو", + output: [ + makePronounSelection(T.Person.SecondPlurMale), + makePronounSelection(T.Person.SecondPlurFemale), + ], + }, + { + input: "ستاسې", + output: [ + makePronounSelection(T.Person.SecondPlurMale), + makePronounSelection(T.Person.SecondPlurFemale), + ], + }, + { + input: "د پلار ستا", + output: "error", + }, +]; + +test("parse possesor", () => { + tests.forEach(({ input, output }) => { + const tokens = tokenizer(input); + const parsed = parsePossesor(tokens, lookup, undefined); + if (output === "error") { + expect(parsed.some((x) => x.errors.length)).toBe(true); + } else { + expect( + parsePossesor(tokens, lookup, undefined) + .filter(isCompleteResult) + .map((x) => x.body.np.selection) + ).toEqual(output); + } + }); +}); diff --git a/src/lib/src/parsing/parse-possesor.ts b/src/lib/src/parsing/parse-possesor.ts new file mode 100644 index 0000000..ad4712f --- /dev/null +++ b/src/lib/src/parsing/parse-possesor.ts @@ -0,0 +1,136 @@ +import * as T from "../../../types"; +import { parseNP } from "./parse-np"; +import { bindParseResult } from "./utils"; +// TODO: maybe contractions should just be male to cut down on the +// alternative sentences +const contractions: [string[], T.Person[]][] = [ + [["زما"], [T.Person.FirstSingMale, T.Person.FirstSingFemale]], + [["ستا"], [T.Person.SecondSingMale, T.Person.SecondSingFemale]], + [ + ["زمونږ", "زموږ"], + [T.Person.FirstPlurMale, T.Person.FirstPlurFemale], + ], + [ + ["ستاسو", "ستاسې"], + [T.Person.SecondPlurMale, T.Person.SecondPlurFemale], + ], +]; + +export function parsePossesor( + tokens: Readonly, + lookup: (s: Partial) => T.DictionaryEntry[], + prevPossesor: T.PossesorSelection | undefined +): T.ParseResult[] { + if (tokens.length === 0) { + if (prevPossesor) { + return [ + { + tokens, + body: prevPossesor, + errors: [], + }, + ]; + } + return []; + } + const [first, ...rest] = tokens; + // parse contraction + // then later (if possessor || contractions) + const contractions = parseContractions(first); + if (contractions.length) { + const errors = prevPossesor + ? [{ message: "a pronoun cannot have a possesor" }] + : []; + return contractions + .flatMap((p) => parsePossesor(rest, lookup, p)) + .map((x) => ({ + ...x, + errors: [...errors, ...x.errors], + })); + } + if (first.s === "د") { + const np = parseNP(rest, lookup); + return bindParseResult(np, (tokens, body) => { + const possesor: T.PossesorSelection = { + shrunken: false, + np: body.selection, + }; + return { + errors: !body.inflected + ? // TODO: get ps to say which possesor + // TODO: track the position coming from the parseNP etc for highlighting + [{ message: `possesor should be inflected` }] + : [], + // add and check error - can't add possesor to pronoun + next: parsePossesor(tokens, lookup, addPoss(prevPossesor, possesor)), + }; + }); + } + if (first.s === "زما") { + return [ + { + tokens: rest, + body: { + shrunken: false, + np: { + type: "NP", + selection: { + type: "pronoun", + distance: "far", + person: T.Person.FirstSingMale, + }, + }, + }, + errors: [], + }, + ]; + } + if (prevPossesor) { + return [ + { + tokens, + body: prevPossesor, + errors: [], + }, + ]; + } + return []; +} + +function addPoss( + possesor: T.PossesorSelection | undefined, + possesorOf: T.PossesorSelection +): T.PossesorSelection { + return { + ...possesorOf, + ...(possesorOf.np.selection.type !== "pronoun" + ? { + np: { + ...possesorOf.np, + selection: { + ...possesorOf.np.selection, + possesor, + }, + }, + } + : {}), + }; +} + +function parseContractions({ s }: T.Token): T.PossesorSelection[] { + const c = contractions.find(([ps]) => ps.includes(s)); + if (!c) { + return []; + } + return c[1].map((person) => ({ + shrunken: false, + np: { + type: "NP", + selection: { + type: "pronoun", + distance: "far", + person, + }, + }, + })); +} diff --git a/src/lib/src/parsing/parse-pronoun.ts b/src/lib/src/parsing/parse-pronoun.ts index 2449548..82c0116 100644 --- a/src/lib/src/parsing/parse-pronoun.ts +++ b/src/lib/src/parsing/parse-pronoun.ts @@ -2,7 +2,7 @@ import * as T from "../../../types"; type Result = ReturnType[number]; -// TODO: map for doubling true, false, and masc fem +// TODO: add chaa export function parsePronoun(tokens: Readonly): T.ParseResult<{ inflected: boolean; selection: T.PronounSelection; @@ -21,6 +21,19 @@ export function parsePronoun(tokens: Readonly): T.ParseResult<{ }, errors: [], })); + } else if (s === "ما") { + return [0, 1].map((person) => ({ + tokens: rest, + body: { + inflected: true, + selection: { + type: "pronoun", + person, + distance: "far", + }, + }, + errors: [], + })); } else if (s === "ته") { return [2, 3].map((person) => ({ tokens: rest, @@ -34,6 +47,19 @@ export function parsePronoun(tokens: Readonly): T.ParseResult<{ }, errors: [], })); + } else if (s === "تا") { + return [2, 3].map((person) => ({ + tokens: rest, + body: { + inflected: true, + selection: { + type: "pronoun", + person, + distance: "far", + }, + }, + errors: [], + })); } else if (s === "هغه") { return [ ...[false, true].map((inflected) => ({ @@ -42,7 +68,7 @@ export function parsePronoun(tokens: Readonly): T.ParseResult<{ inflected, selection: { type: "pronoun", - person: 5, + person: 4, distance: "far", }, }, @@ -54,7 +80,7 @@ export function parsePronoun(tokens: Readonly): T.ParseResult<{ inflected: false, selection: { type: "pronoun", - person: 5, + person: 4, distance: "far", }, }, diff --git a/src/lib/src/parsing/parse-verb.ts b/src/lib/src/parsing/parse-verb.ts new file mode 100644 index 0000000..8a6213d --- /dev/null +++ b/src/lib/src/parsing/parse-verb.ts @@ -0,0 +1,64 @@ +import * as T from "../../../types"; + +export function parseVerb( + tokens: Readonly, + verbLookup: (s: (e: T.VerbDictionaryEntry) => boolean) => T.VerbEntry[] +): T.ParseResult>[] { + if (tokens.length === 0) { + return []; + } + const [first, ...rest] = tokens; + const people = getVerbEnding(first.s); + if (people.length === 0) { + return []; + } + const verbs = findByStem(first.s.slice(0, -1), verbLookup); + + return people.flatMap((person) => + verbs.map((verb) => ({ + tokens: rest, + body: { + type: "VB", + person, + info: { + type: "verb", + aspect: "imperfective", + base: "stem", + verb, + }, + }, + errors: [], + })) + ); +} + +function getVerbEnding(p: string): T.Person[] { + if (p.endsWith("م")) { + return [T.Person.FirstSingMale, T.Person.FirstSingFemale]; + } else if (p.endsWith("ې")) { + return [T.Person.SecondSingMale, T.Person.SecondSingFemale]; + } else if (p.endsWith("ي")) { + return [ + T.Person.ThirdSingMale, + T.Person.ThirdSingFemale, + T.Person.ThirdPlurMale, + T.Person.ThirdPlurFemale, + ]; + } else if (p.endsWith("و")) { + return [T.Person.FirstPlurMale, T.Person.FirstPlurFemale]; + } else if (p.endsWith("ئ")) { + return [T.Person.SecondPlurMale, T.Person.SecondPlurFemale]; + } + return []; +} + +function findByStem( + stem: string, + verbLookup: (s: (e: T.VerbDictionaryEntry) => boolean) => T.VerbEntry[] +): T.VerbEntry[] { + return verbLookup( + (e) => + e.psp === stem || + (!e.psp && !e.c.includes("comp") && e.p.slice(0, -1) === stem) + ); +} diff --git a/src/lib/src/parsing/parse-vp.ts b/src/lib/src/parsing/parse-vp.ts new file mode 100644 index 0000000..65d9e35 --- /dev/null +++ b/src/lib/src/parsing/parse-vp.ts @@ -0,0 +1,117 @@ +import * as T from "../../../types"; +import { parseNP } from "./parse-np"; +import { bindParseResult } from "./utils"; +import { parseVerb } from "./parse-verb"; +import { + makeObjectSelectionComplete, + makeSubjectSelectionComplete, +} from "../phrase-building/blocks-utils"; +import { vEntry } from "../new-verb-engine/rs-helpers"; +import { getPersonFromNP, isThirdPerson } from "../phrase-building/vp-tools"; +// to hide equatives type-doubling issue +const kedulStat = vEntry({ + ts: 1581086654898, + i: 11100, + p: "کېدل", + f: "kedul", + g: "kedul", + e: "to become _____", + r: 2, + c: "v. intrans.", + ssp: "ش", + ssf: "sh", + prp: "شول", + prf: "shwul", + pprtp: "شوی", + pprtf: "shúway", + noOo: true, + ec: "become", +}); + +export function parseVP( + tokens: Readonly, + lookup: (s: Partial) => T.DictionaryEntry[], + verbLookup: (s: (e: T.VerbDictionaryEntry) => boolean) => T.VerbEntry[] +): T.ParseResult[] { + if (tokens.length === 0) { + return []; + } + // how to make this into a nice pipeline... 🤔 + const NP1 = parseNP(tokens, lookup); + const NP2 = bindParseResult(NP1, (tokens) => parseNP(tokens, lookup), true); + const vb = bindParseResult( + NP2, + (tokens) => parseVerb(tokens, verbLookup), + true + ); + // TODO: be able to bind mulitple vals + return bindParseResult, T.VPSelectionComplete>( + vb, + (tokens, v) => { + const w: T.ParseResult[] = []; + NP1.forEach(({ body: np1 }) => { + NP2.forEach(({ body: np2 }) => { + [ + [np1, np2], + [np2, np1], + ].forEach(([s, o]) => { + const errors: T.ParseError[] = []; + const subjPerson = getPersonFromNP(s.selection); + if (s.inflected) { + errors.push({ message: "subject should not be inflected" }); + } + if (o.selection.selection.type === "pronoun") { + if (!isThirdPerson(subjPerson) && !o.inflected) { + errors.push({ + message: + "1st or 2nd person object pronoun should be inflected", + }); + } + } else if (o.inflected) { + errors.push({ message: "object should not be inflected" }); + } + if (getPersonFromNP(s.selection) !== v.person) { + errors.push({ message: "verb does not match subject" }); + } + const blocks: T.VPSBlockComplete[] = [ + { + key: 1, + block: makeSubjectSelectionComplete(s.selection), + }, + { + key: 2, + block: makeObjectSelectionComplete(o.selection), + }, + ]; + const verb: T.VerbSelectionComplete = { + type: "verb", + verb: v.info.type === "verb" ? v.info.verb : kedulStat, + transitivity: "transitive", + canChangeTransitivity: false, + canChangeStatDyn: false, + negative: false, + tense: "presentVerb", + canChangeVoice: true, + isCompound: false, + voice: "active", + }; + w.push({ + tokens, + body: { + blocks, + verb, + externalComplement: undefined, + form: { + removeKing: false, + shrinkServant: false, + }, + }, + errors, + }); + }); + }); + }); + return w; + } + ); +} diff --git a/src/lib/src/parsing/tokenizer.ts b/src/lib/src/parsing/tokenizer.ts index bead23f..943cf9f 100644 --- a/src/lib/src/parsing/tokenizer.ts +++ b/src/lib/src/parsing/tokenizer.ts @@ -1,7 +1,8 @@ import { Token } from "../../../types"; +import { standardizePashto } from "../standardize-pashto"; export function tokenizer(s: string): Token[] { - const words = s.trim().split(/ +/); + const words = standardizePashto(s).trim().split(/ +/); const indexed: { i: number; s: string }[] = []; for (let i = 0; i < words.length; i++) { indexed.push({ i, s: words[i] }); diff --git a/src/lib/src/parsing/utils.ts b/src/lib/src/parsing/utils.ts new file mode 100644 index 0000000..ad8fc33 --- /dev/null +++ b/src/lib/src/parsing/utils.ts @@ -0,0 +1,81 @@ +import * as T from "../../../types"; + +/** + * Monadic binding for ParseResult[] + * + * Takes a given array of parse results + * and a function to take the tokens and body of each parse result + * and do something further with them + * + * all the results are flatMapped into a new ParseResult[] monad + * and the errors are passed on and pruned + * + * @param previous - the set of results (monad) to start with + * @param f - a function that takes a remaining list of tokens and one body of the previous result + * and returns the next set of possible results, optionally with an object containing any errors + * @param getCritical - if needed, a function that returns with *part* of the result body to compare + * for identical results while pruning out the unneccesary errors + * @param ignorePrevious - pass in true if you don't need the previous ParseResult to calculate + * the next one. This will add effeciancy by only caring about how many tokens are available + * from the different previous results + * @returns + */ +export function bindParseResult( + previous: T.ParseResult[], + f: ( + tokens: Readonly, + r: C + ) => + | T.ParseResult[] + | { + errors: T.ParseError[]; + next: T.ParseResult[]; + }, + ignorePrevious?: boolean +): T.ParseResult[] { + // const prev = ignorePrevious + // ? (() => { + // const resArr: T.ParseResult[] = []; + // previous.filter((item) => { + // var i = resArr.findIndex( + // (x) => x.tokens.length === item.tokens.length + // ); + // if (i <= -1) { + // resArr.push(item); + // } + // return null; + // }); + // return resArr; + // })() + // : previous; + const prev = previous; + const nextPossibilities = prev.flatMap(({ tokens, body, errors }) => { + const res = f(tokens, body); + const { errors: errsPassed, next } = Array.isArray(res) + ? { errors: [], next: res } + : res; + return next.map((x) => ({ + tokens: x.tokens, + body: x.body, + errors: [...errsPassed, ...x.errors, ...errors], + })); + }); + return cleanOutFails(nextPossibilities); +} + +export function cleanOutFails( + results: T.ParseResult[] +): T.ParseResult[] { + // if there's any success anywhere, remove any of the errors + const errorsGone = results.find((x) => x.errors.length === 0) + ? results.filter((x) => x.errors.length === 0) + : results; + // @ts-ignore + return Array.from(new Set(errorsGone.map(JSON.stringify))).map(JSON.parse); +} + +export function isCompleteResult( + r: T.ParseResult +): boolean { + return !r.tokens.length && !r.errors.length; +} diff --git a/src/lib/src/phrase-building/blocks-utils.ts b/src/lib/src/phrase-building/blocks-utils.ts index 72b7cb8..af13c3f 100644 --- a/src/lib/src/phrase-building/blocks-utils.ts +++ b/src/lib/src/phrase-building/blocks-utils.ts @@ -157,6 +157,15 @@ export function makeSubjectSelection( }; } +export function makeSubjectSelectionComplete( + selection: T.NPSelection +): T.SubjectSelectionComplete { + return { + type: "subjectSelection", + selection, + }; +} + export function makeObjectSelection( selection: | T.ObjectSelection @@ -195,6 +204,15 @@ export function makeObjectSelection( }; } +export function makeObjectSelectionComplete( + selection: T.NPSelection +): T.ObjectSelectionComplete { + return { + type: "objectSelection", + selection, + }; +} + export function EPSBlocksAreComplete( blocks: T.EPSBlock[] ): blocks is T.EPSBlockComplete[] { diff --git a/src/lib/src/phrase-building/compile.ts b/src/lib/src/phrase-building/compile.ts index e1141af..ab5c568 100644 --- a/src/lib/src/phrase-building/compile.ts +++ b/src/lib/src/phrase-building/compile.ts @@ -344,9 +344,7 @@ function getPsFromPiece( } function getPsFromWelded(v: T.Welded): T.PsString[] { - function getPsFromSide( - v: T.VBBasic | T.Welded | T.NComp | T.VBGenNum - ): T.PsString[] { + function getPsFromSide(v: T.VB | T.NComp): T.PsString[] { if (v.type === "VB") { return flattenLengths(v.ps); } diff --git a/src/lib/src/phrase-building/make-selections.ts b/src/lib/src/phrase-building/make-selections.ts index 878de9f..906665b 100644 --- a/src/lib/src/phrase-building/make-selections.ts +++ b/src/lib/src/phrase-building/make-selections.ts @@ -31,6 +31,29 @@ export function makeAdjectiveSelection( }; } +export function makePossesorSelection( + np: T.NPSelection["selection"] +): T.PossesorSelection { + return { + shrunken: false, + np: { + type: "NP", + selection: np, + }, + }; +} + +export function makePronounSelection( + person: T.Person, + distance?: "near" | "far" +): T.PronounSelection { + return { + type: "pronoun", + distance: distance || "far", + person, + }; +} + export function makeParticipleSelection( verb: T.VerbEntry ): T.ParticipleSelection { diff --git a/src/lib/src/phrase-building/np-tools.ts b/src/lib/src/phrase-building/np-tools.ts index bd60782..4f00039 100644 --- a/src/lib/src/phrase-building/np-tools.ts +++ b/src/lib/src/phrase-building/np-tools.ts @@ -1,239 +1,311 @@ -import { - isFirstPerson, - isSecondPerson, -} from "../misc-helpers"; +import { isFirstPerson, isSecondPerson } from "../misc-helpers"; import * as T from "../../../types"; import { concatPsString } from "../p-text-helpers"; -function getBaseAndAdjectives({ selection }: T.Rendered): T.PsString[] { - if (selection.type === "sandwich") { - return getSandwichPsBaseAndAdjectives(selection); - } - const adjs = "adjectives" in selection && selection.adjectives; - if (!adjs) { - return selection.ps; - } - return selection.ps.map(p => ( - concatPsString( - adjs.reduce((accum, curr) => ( - // TODO: with variations of adjs? - concatPsString(accum, (accum.p === "" && accum.f === "") ? "" : " ", curr.ps[0]) - ), { p: "", f: "" }), - " ", - p, - ) - )); +function getBaseAndAdjectives({ + selection, +}: T.Rendered< + T.NPSelection | T.ComplementSelection | T.APSelection +>): T.PsString[] { + if (selection.type === "sandwich") { + return getSandwichPsBaseAndAdjectives(selection); + } + const adjs = "adjectives" in selection && selection.adjectives; + if (!adjs) { + return selection.ps; + } + return selection.ps.map((p) => + concatPsString( + adjs.reduce( + (accum, curr) => + // TODO: with variations of adjs? + concatPsString( + accum, + accum.p === "" && accum.f === "" ? "" : " ", + curr.ps[0] + ), + { p: "", f: "" } + ), + " ", + p + ) + ); } -function getSandwichPsBaseAndAdjectives(s: T.Rendered>): T.PsString[] { - const insideBase = getBaseAndAdjectives(s.inside); - const willContractWithPronoun = s.before && s.before.p === "د" && s.inside.selection.type === "pronoun" - && (isFirstPerson(s.inside.selection.person) || isSecondPerson(s.inside.selection.person)) - const contracted = (willContractWithPronoun && s.inside.selection.type === "pronoun") - ? contractPronoun(s.inside.selection) - : undefined - return insideBase.map((inside) => ( - concatPsString( - (s.before && !willContractWithPronoun) ? s.before : "", - s.before ? " " : "", - contracted ? contracted : inside, - s.after ? " " : "", - s.after ? s.after : "", - ) - )); +function getSandwichPsBaseAndAdjectives( + s: T.Rendered> +): T.PsString[] { + const insideBase = getBaseAndAdjectives(s.inside); + const willContractWithPronoun = + s.before && + s.before.p === "د" && + s.inside.selection.type === "pronoun" && + (isFirstPerson(s.inside.selection.person) || + isSecondPerson(s.inside.selection.person)); + const contracted = + willContractWithPronoun && s.inside.selection.type === "pronoun" + ? contractPronoun(s.inside.selection) + : undefined; + return insideBase.map((inside) => + concatPsString( + s.before && !willContractWithPronoun ? s.before : "", + s.before ? " " : "", + contracted ? contracted : inside, + s.after ? " " : "", + s.after ? s.after : "" + ) + ); } -function contractPronoun(n: T.Rendered): T.PsString | undefined { - return isFirstPerson(n.person) - ? concatPsString({ p: "ز", f: "z" }, n.ps[0]) - : isSecondPerson(n.person) - ? concatPsString({ p: "س", f: "s" }, n.ps[0]) - : undefined; +function contractPronoun( + n: T.Rendered +): T.PsString | undefined { + return isFirstPerson(n.person) + ? concatPsString({ p: "ز", f: "z" }, n.ps[0]) + : isSecondPerson(n.person) + ? concatPsString({ p: "س", f: "s" }, n.ps[0]) + : undefined; } -function trimOffShrunkenPossesive(p: T.Rendered): T.Rendered { - if (!("possesor" in p.selection)) { - return p; - } - if (!p.selection.possesor) { - return p; - } - if (p.selection.possesor.shrunken) { - return { - type: "NP", - selection: { - ...p.selection, - possesor: undefined, - }, - }; - } +function trimOffShrunkenPossesive( + p: T.Rendered +): T.Rendered { + if (!("possesor" in p.selection)) { + return p; + } + if (!p.selection.possesor) { + return p; + } + if (p.selection.possesor.shrunken) { return { - type: "NP", - selection: { - ...p.selection, - possesor: { - ...p.selection.possesor, - np: trimOffShrunkenPossesive(p.selection.possesor.np), - }, - }, + type: "NP", + selection: { + ...p.selection, + possesor: undefined, + }, }; + } + return { + type: "NP", + selection: { + ...p.selection, + possesor: { + ...p.selection.possesor, + np: trimOffShrunkenPossesive(p.selection.possesor.np), + }, + }, + }; } -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; +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; + } + if (b.selection.type === "adjective") { + if (!b.selection.sandwich) { + return base; } - if (b.selection.type === "adjective") { - 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 => ( - concatPsString(s, " ", p) - )) - )); - } - const trimmed = b.selection.type === "sandwich" ? { - type: b.type, - selection: { + // TODO: Kinda cheating + const sandwichPs = getPashtoFromRendered( + { type: "AP", selection: b.selection.sandwich }, + false + ); + return base.flatMap((p) => + sandwichPs.flatMap((s) => concatPsString(s, " ", p)) + ); + } + const trimmed = + b.selection.type === "sandwich" + ? { + type: b.type, + selection: { ...b.selection, inside: trimOffShrunkenPossesive(b.selection.inside), - }, - } : trimOffShrunkenPossesive({ type: "NP", selection: b.selection }); - if (trimmed.selection.type === "sandwich") { - return trimmed.selection.inside.selection.possesor - ? addPossesor(trimmed.selection.inside.selection.possesor.np, base, subjectsPerson) - : base; - } - if (trimmed.selection.possesor) { - return addPossesor(trimmed.selection.possesor.np, base, subjectsPerson); - } - return base; + }, + } + : trimOffShrunkenPossesive({ type: "NP", selection: b.selection }); + if (trimmed.selection.type === "sandwich") { + return trimmed.selection.inside.selection.possesor + ? addPossesor( + trimmed.selection.inside.selection.possesor.np, + base, + subjectsPerson + ) + : base; + } + if (trimmed.selection.possesor) { + return addPossesor(trimmed.selection.possesor.np, base, subjectsPerson); + } + return base; } -function addPossesor(owner: T.Rendered, existing: T.PsString[], subjectsPerson: false | T.Person): T.PsString[] { - function willBeReflexive(subj: T.Person, obj: T.Person): boolean { - return ( - ([0, 1].includes(subj) && [0, 1].includes(obj)) - || - ([2, 3].includes(subj) && [8, 9].includes(obj)) - ); - } - const wPossesor = existing.flatMap(ps => ( - getBaseAndAdjectives(owner).map(v => ( - (owner.selection.type === "pronoun" && subjectsPerson !== false && willBeReflexive(subjectsPerson, owner.selection.person)) - ? concatPsString({ p: "خپل", f: "khpul" }, " ", ps) - : (owner.selection.type === "pronoun" && isFirstPerson(owner.selection.person)) - ? concatPsString({ p: "ز", f: "z" }, v, " ", ps) - : (owner.selection.type === "pronoun" && isSecondPerson(owner.selection.person)) - ? concatPsString({ p: "س", f: "s" }, v, " ", ps) - : concatPsString({ p: "د", f: "du" }, " ", v, " ", ps) - )) - )); - if (!owner.selection.possesor) { - return wPossesor; - } - return addPossesor(owner.selection.possesor.np, wPossesor, subjectsPerson); +function addPossesor( + owner: T.Rendered, + existing: T.PsString[], + subjectsPerson: false | T.Person +): T.PsString[] { + function willBeReflexive(subj: T.Person, obj: T.Person): boolean { + return ( + ([0, 1].includes(subj) && [0, 1].includes(obj)) || + ([2, 3].includes(subj) && [8, 9].includes(obj)) + ); + } + const wPossesor = existing.flatMap((ps) => + getBaseAndAdjectives(owner).map((v) => + owner.selection.type === "pronoun" && + subjectsPerson !== false && + willBeReflexive(subjectsPerson, owner.selection.person) + ? concatPsString({ p: "خپل", f: "khpul" }, " ", ps) + : owner.selection.type === "pronoun" && + isFirstPerson(owner.selection.person) + ? concatPsString({ p: "ز", f: "z" }, v, " ", ps) + : owner.selection.type === "pronoun" && + isSecondPerson(owner.selection.person) + ? concatPsString({ p: "س", f: "s" }, v, " ", ps) + : concatPsString({ p: "د", f: "du" }, " ", v, " ", ps) + ) + ); + if (!owner.selection.possesor) { + return wPossesor; + } + return addPossesor(owner.selection.possesor.np, wPossesor, subjectsPerson); } -function addArticlesAndAdjs(np: T.Rendered): string | undefined { - if (!np.e) return undefined; - try { - // split out the atricles so adjectives can be stuck inbetween them and the word - const chunks = np.e.split("the)"); - const [articles, word] = chunks.length === 1 - ? ["", np.e] - : [chunks[0] + "the) ", chunks[1]]; - const adjs = !np.adjectives - ? "" - : np.adjectives.reduce((accum, curr): string => { - if (!curr.e) throw new Error("no english for adjective"); - return accum + curr.e + " "; - }, ""); - const genderTag = np.genderCanChange ? (np.gender === "fem" ? " (f.)" : " (m.)") : ""; - return `${articles}${adjs}${word}${genderTag}`; - } catch (e) { - return undefined; - } +function addArticlesAndAdjs( + np: T.Rendered +): string | undefined { + if (!np.e) return undefined; + try { + // split out the atricles so adjectives can be stuck inbetween them and the word + const chunks = np.e.split("the)"); + const [articles, word] = + chunks.length === 1 ? ["", np.e] : [chunks[0] + "the) ", chunks[1]]; + const adjs = !np.adjectives + ? "" + : np.adjectives.reduce((accum, curr): string => { + if (!curr.e) throw new Error("no english for adjective"); + return accum + curr.e + " "; + }, ""); + const genderTag = np.genderCanChange + ? np.gender === "fem" + ? " (f.)" + : " (m.)" + : ""; + return `${articles}${adjs}${word}${genderTag}`; + } catch (e) { + return undefined; + } } -function addPossesors(possesor: T.Rendered | undefined, base: string | undefined, type: "noun" | "participle"): string | undefined { - function removeArticles(s: string): string { - return s.replace("(the) ", "").replace("(a/the) ", ""); - } - if (!base) return undefined; - if (!possesor) return base; - if (possesor.selection.type === "pronoun") { - return type === "noun" - ? `${pronounPossEng(possesor.selection.person)} ${removeArticles(base)}` - : `(${pronounPossEng(possesor.selection.person)}) ${removeArticles(base)} (${possesor.selection.e})` - } - const possesorE = getEnglishFromRendered(possesor); - if (!possesorE) return undefined; - const withApostrophe = `${possesorE}'${possesorE.endsWith("s") ? "" : "s"}`; +function addPossesors( + possesor: T.Rendered | undefined, + base: string | undefined, + type: "noun" | "participle" +): string | undefined { + function removeArticles(s: string): string { + return s.replace("(the) ", "").replace("(a/the) ", ""); + } + if (!base) return undefined; + if (!possesor) return base; + if (possesor.selection.type === "pronoun") { return type === "noun" - ? `${withApostrophe} ${removeArticles(base)}` - : `(${withApostrophe}) ${removeArticles(base)} (${possesorE})`; + ? `${pronounPossEng(possesor.selection.person)} ${removeArticles(base)}` + : `(${pronounPossEng(possesor.selection.person)}) ${removeArticles( + base + )} (${possesor.selection.e})`; + } + const possesorE = getEnglishFromRendered(possesor); + if (!possesorE) return undefined; + const withApostrophe = `${possesorE}'${possesorE.endsWith("s") ? "" : "s"}`; + return type === "noun" + ? `${withApostrophe} ${removeArticles(base)}` + : `(${withApostrophe}) ${removeArticles(base)} (${possesorE})`; } function pronounPossEng(p: T.Person): string { - if (p === T.Person.FirstSingMale || p === T.Person.FirstSingFemale) { - return "my"; - } - if (p === T.Person.FirstPlurMale || p === T.Person.FirstPlurFemale) { - return "our"; - } - if (p === T.Person.SecondSingMale || p === T.Person.SecondSingFemale) { - return "your"; - } - if (p === T.Person.SecondPlurMale || p === T.Person.SecondPlurFemale) { - return "your (pl.)"; - } - if (p === T.Person.ThirdSingMale) { - return "his/its"; - } - if (p === T.Person.ThirdSingFemale) { - return "her/its"; - } - return "their"; + function gend(x: T.Person): string { + return `${x % 2 === 0 ? "m." : "f."}`; + } + if (p === T.Person.FirstSingMale || p === T.Person.FirstSingFemale) { + return `my (${gend(p)})`; + } + if (p === T.Person.FirstPlurMale || p === T.Person.FirstPlurFemale) { + return `our (${gend(p)})`; + } + if (p === T.Person.SecondSingMale || p === T.Person.SecondSingFemale) { + return `your (${gend(p)})`; + } + if (p === T.Person.SecondPlurMale || p === T.Person.SecondPlurFemale) { + return `your (${gend(p)} pl.)`; + } + if (p === T.Person.ThirdSingMale) { + return "his/its"; + } + if (p === T.Person.ThirdSingFemale) { + return "her/its"; + } + return `their ${gend(p)}`; } -export function getEnglishFromRendered(r: T.Rendered>): string | undefined { - if (r.type === "sandwich") { - return getEnglishFromRenderedSandwich(r); - } - if (r.selection.type === "sandwich") { - return getEnglishFromRenderedSandwich(r.selection); - } - if (!r.selection.e) return undefined; - if (r.selection.type === "loc. adv." || r.selection.type === "adverb") { - return r.selection.e; - } - if (r.selection.type === "adjective") { - return getEnglishFromRenderedAdjective(r.selection); - } - if (r.selection.type === "pronoun") { - return r.selection.e; - } - if (r.selection.type === "participle") { - return addPossesors(r.selection.possesor?.np, r.selection.e, r.selection.type); - } - return addPossesors(r.selection.possesor?.np, addArticlesAndAdjs(r.selection), r.selection.type); +export function getEnglishFromRendered( + r: T.Rendered< + | T.NPSelection + | T.ComplementSelection + | T.APSelection + | T.SandwichSelection + > +): string | undefined { + if (r.type === "sandwich") { + return getEnglishFromRenderedSandwich(r); + } + if (r.selection.type === "sandwich") { + return getEnglishFromRenderedSandwich(r.selection); + } + if (!r.selection.e) return undefined; + if (r.selection.type === "loc. adv." || r.selection.type === "adverb") { + return r.selection.e; + } + if (r.selection.type === "adjective") { + return getEnglishFromRenderedAdjective(r.selection); + } + if (r.selection.type === "pronoun") { + return r.selection.e; + } + if (r.selection.type === "participle") { + return addPossesors( + r.selection.possesor?.np, + r.selection.e, + r.selection.type + ); + } + return addPossesors( + r.selection.possesor?.np, + addArticlesAndAdjs(r.selection), + r.selection.type + ); } -function getEnglishFromRenderedSandwich(r: T.Rendered>): string | undefined { - const insideE = getEnglishFromRendered(r.inside); - if (!insideE) return undefined; - return `${r.e} ${insideE}`; +function getEnglishFromRenderedSandwich( + r: T.Rendered> +): string | undefined { + const insideE = getEnglishFromRendered(r.inside); + if (!insideE) return undefined; + return `${r.e} ${insideE}`; } -function getEnglishFromRenderedAdjective(a: T.Rendered): string | undefined { - if (!a.sandwich) { - return a.e; - } - if (!a.e) return undefined; - return `${a.e} ${getEnglishFromRenderedSandwich(a.sandwich)}`; -} \ No newline at end of file +function getEnglishFromRenderedAdjective( + a: T.Rendered +): string | undefined { + if (!a.sandwich) { + return a.e; + } + if (!a.e) return undefined; + return `${a.e} ${getEnglishFromRenderedSandwich(a.sandwich)}`; +} diff --git a/src/types.ts b/src/types.ts index efe1b8c..e821faf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -721,6 +721,14 @@ export type EquativeTense = | "wouldBe" | "pastSubjunctive" | "wouldHaveBeen"; +export type EquativeTenseWithoutBa = + | "present" + | "subjunctive" + | "habitual" + | "past" + | "wouldBe" + | "pastSubjunctive" + | "wouldHaveBeen"; export type PerfectTense = `${EquativeTense}Perfect`; export type AbilityTense = `${VerbTense}Modal`; export type ImperativeTense = `${Aspect}Imperative`; @@ -1201,16 +1209,50 @@ export type RenderVerbOutput = { hasBa: boolean; vbs: VerbRenderedOutput; }; -export type VerbRenderedOutput = [[VHead] | [], [VB, VBE] | [VBE]]; -export type RootsStemsOutput = [[VHead] | [], [VB, VBA] | [VBA]]; // or perfect / equative +export type VerbRenderedOutput = [[VHead] | [], [VBP, VBE] | [VBE]]; +export type RootsStemsOutput = [[VHead] | [], [VBP, VB] | [VB]]; // or perfect / equative -export type VB = VBBasic | VBGenNum | Welded | WeldedGN; -/** A VB block that can have endings attached to it */ -export type VBA = Exclude; +export type VB = VBBasic | Welded; /** A VB block that has had a person verb ending attached */ -export type VBE = (VBBasic | Welded) & { +export type VBE = VB & { person: Person; -}; // or equative + info: + | { + type: "equative"; + tense: EquativeTenseWithoutBa; + } + | { + type: "verb"; + aspect: Aspect; + base: "stem" | "root"; + verb: VerbEntry; + abilityAux?: boolean; + }; +}; + +/** A VB block used for ability verbs or perfect (past participle) + * get optionally swapped in order with the VBE when used with negative + */ +export type VBP = VB & (VBPartInfo | VBAbilityInfo); + +export type VBPartInfo = { + info: { + type: "ppart"; + genNum: GenderNumber; + verb: VerbEntry; + }; +}; + +export type VBAbilityInfo = { + info: { + type: "ability"; + verb: VerbEntry; + aspect: Aspect; + }; +}; + +// in VB OR VBE - add root / stem and entry for parsing info +// but how would that work with perfect and ability verbs ... export type VBNoLenghts = V extends VBBasic ? Omit & { ps: PsString[] } @@ -1221,10 +1263,6 @@ export type VBBasic = { ps: SingleOrLengthOpts; }; -// TODO: might be a better design decision to keep the GenderNuber stuff -// in the RIGHT side of the weld -export type VBGenNum = VBBasic & GenderNumber; - export type GenderNumber = { gender: Gender; number: NounNumber; @@ -1233,11 +1271,9 @@ export type GenderNumber = { export type Welded = { type: "welded"; left: NComp | VBBasic | Welded; - right: VBBasic; + right: VBBasic | (VBBasic & (VBPartInfo | VBAbilityInfo)); }; -export type WeldedGN = Omit & { right: VBGenNum }; - export type VHead = PH | NComp; /** perfective head block */ diff --git a/vocab/nouns-adjs/irreg-nouns.js b/vocab/nouns-adjs/irreg-nouns.js index bbe7f42..728e4f6 100644 --- a/vocab/nouns-adjs/irreg-nouns.js +++ b/vocab/nouns-adjs/irreg-nouns.js @@ -27,4 +27,12 @@ module.exports = [ ts: 1527815450, e: "son", // زوی }, + { + ts: 1527823093, + e: "prophet", // نبي + }, + { + ts: 1527822456, + e: "word", // لفظ + }, ]; diff --git a/vocab/verbs/simple-intrans.js b/vocab/verbs/simple-intrans.js index 7ccfaa2..6e070e3 100644 --- a/vocab/verbs/simple-intrans.js +++ b/vocab/verbs/simple-intrans.js @@ -7,6 +7,7 @@ */ module.exports = [ + 1527815139, // osedul 1585228579997, // ورتلل 1527815216, // راتلل - to come 1527813473, // الوتل - to fly diff --git a/vocab/verbs/stative-comp-trans.js b/vocab/verbs/stative-comp-trans.js index 2fdc4f3..7bf082d 100644 --- a/vocab/verbs/stative-comp-trans.js +++ b/vocab/verbs/stative-comp-trans.js @@ -7,6 +7,8 @@ */ module.exports = [ + 1527817457, // درکول + 1659037345120, // بیانېدل 1608137130992, // چیغه کول 1658537998960, // لېونی کول 1527812403, // بچ کول - to save, protect, guard, spare, rescue, economize