diff --git a/src/lib/src/parsing/parse-blocks.ts b/src/lib/src/parsing/parse-blocks.ts index 3a53341..bb98028 100644 --- a/src/lib/src/parsing/parse-blocks.ts +++ b/src/lib/src/parsing/parse-blocks.ts @@ -1,8 +1,10 @@ import * as T from "../../../types"; import { LookupFunction } from "./lookup"; +import { parseEquative } from "./parse-equative"; import { parseKidsSection } from "./parse-kids-section"; import { parseNeg } from "./parse-negative"; import { parseNP } from "./parse-np"; +import { parsePastPart } from "./parse-past-part"; import { parsePH } from "./parse-ph"; import { parseVerb } from "./parse-verb"; import { bindParseResult, returnParseResult } from "./utils"; @@ -26,6 +28,8 @@ export function parseBlocks( const np = prevPh ? [] : parseNP(tokens, lookup); const ph = vbExists || prevPh ? [] : parsePH(tokens); const vb = parseVerb(tokens, lookup); + const vbp = parsePastPart(tokens, lookup); + const eq = parseEquative(tokens); const neg = parseNeg(tokens); const kidsR = parseKidsSection(tokens, []); const allResults: T.ParseResult[] = [ @@ -33,6 +37,8 @@ export function parseBlocks( ...ph, ...neg, ...vb, + ...vbp, + ...eq, ...kidsR, ]; // TODO: is this necessary? @@ -76,7 +82,10 @@ export function parseBlocks( }); } -function phMatches(ph: T.ParsedPH | undefined, vb: T.ParsedVBE | undefined) { +function phMatches( + ph: T.ParsedPH | undefined, + vb: T.ParsedVBE | T.ParsedVBP | undefined +) { if (!ph) { return true; } diff --git a/src/lib/src/parsing/parse-equative.ts b/src/lib/src/parsing/parse-equative.ts new file mode 100644 index 0000000..fa77056 --- /dev/null +++ b/src/lib/src/parsing/parse-equative.ts @@ -0,0 +1,106 @@ +import * as T from "../../../types"; + +export function parseEquative( + tokens: Readonly +): T.ParseResult[] { + if (tokens.length === 0) { + return []; + } + const [{ s }, ...rest] = tokens; + const match = table.find((x) => x.ps.includes(s)); + if (!match) { + return []; + } + return match.people.flatMap((person) => + match.tenses.map((tense) => ({ + tokens: rest, + body: { + type: "VB", + info: { + type: "equative", + tense, + }, + person, + }, + errors: [], + })) + ); +} + +// TODO: NOT COMPLETE / CORRECT +const table: { + ps: string[]; + tenses: T.EquativeTenseWithoutBa[]; + people: T.Person[]; +}[] = [ + { + ps: ["یم"], + tenses: ["present", "habitual"], + people: [T.Person.FirstSingMale, T.Person.FirstSingFemale], + }, + { + ps: ["یې"], + tenses: ["present", "habitual"], + people: [T.Person.SecondSingMale, T.Person.SecondSingFemale], + }, + { + ps: ["یو"], + tenses: ["present", "habitual"], + people: [T.Person.FirstPlurMale, T.Person.FirstPlurFemale], + }, + { + ps: ["یئ"], + tenses: ["present", "habitual"], + people: [T.Person.SecondPlurMale, T.Person.SecondPlurFemale], + }, + { + ps: ["وم"], + tenses: ["subjunctive", "past"], + people: [T.Person.FirstSingMale, T.Person.FirstSingFemale], + }, + { + ps: ["وې"], + tenses: ["subjunctive", "past"], + people: [T.Person.SecondSingMale, T.Person.SecondSingFemale], + }, + { + ps: ["وو"], + tenses: ["subjunctive", "past"], + people: [T.Person.FirstPlurMale, T.Person.FirstPlurFemale], + }, + { + ps: ["وئ"], + tenses: ["subjunctive", "past"], + people: [T.Person.SecondPlurMale, T.Person.SecondPlurFemale], + }, + { + ps: ["دی"], + tenses: ["present"], + people: [T.Person.ThirdSingMale], + }, + { + ps: ["ده"], + tenses: ["present"], + people: [T.Person.ThirdSingFemale], + }, + { + ps: ["دي"], + tenses: ["present"], + people: [T.Person.ThirdPlurMale, T.Person.ThirdPlurFemale], + }, + { + ps: ["وي"], + tenses: ["habitual"], + people: [ + T.Person.ThirdSingMale, + T.Person.ThirdSingFemale, + T.Person.ThirdPlurMale, + T.Person.ThirdPlurFemale, + ], + }, + { + ps: ["وای", "وی"], + tenses: ["pastSubjunctive"], + people: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + }, +]; diff --git a/src/lib/src/parsing/parse-past-part.ts b/src/lib/src/parsing/parse-past-part.ts new file mode 100644 index 0000000..b7bfb11 --- /dev/null +++ b/src/lib/src/parsing/parse-past-part.ts @@ -0,0 +1,64 @@ +import * as T from "../../../types"; +import { LookupFunction } from "./lookup"; +import { returnParseResult } from "./utils"; + +export function parsePastPart( + tokens: Readonly, + lookup: LookupFunction +): T.ParseResult[] { + if (tokens.length === 0) { + return []; + } + const [{ s }, ...rest] = tokens; + const ending: "ی" | "ي" | "ې" = s.at(-1) as "ی" | "ي" | "ې"; + if (!ending || !["ی", "ي", "ې"].includes(ending)) { + return []; + } + // TODO: ALSO HANDLE SHORT FORMS + const wOutEnd = s.slice(0, -1); + const matches = lookup(wOutEnd, "participle"); + const genNums = endingGenderNum(ending); + return matches + .flatMap((verb) => + genNums.map((genNum) => ({ + type: "VB", + info: { + type: "ppart", + verb, + genNum, + }, + })) + ) + .flatMap((m) => returnParseResult(rest, m)); +} + +function endingGenderNum(ending: "ی" | "ي" | "ې"): T.GenderNumber[] { + if (ending === "ی") { + return [ + { + gender: "masc", + number: "singular", + }, + ]; + } + if (ending === "ي") { + return [ + { + gender: "masc", + number: "plural", + }, + ]; + } + // if (ending === "ې") { + return [ + { + gender: "fem", + number: "singular", + }, + { + gender: "fem", + number: "plural", + }, + ]; + // } +} diff --git a/src/lib/src/parsing/parse-vp.test.ts b/src/lib/src/parsing/parse-vp.test.ts index 9d9faef..2171054 100644 --- a/src/lib/src/parsing/parse-vp.test.ts +++ b/src/lib/src/parsing/parse-vp.test.ts @@ -22,6 +22,7 @@ const leedul = wordQuery("لیدل", "verb"); const kenaastul = wordQuery("کېناستل", "verb"); const wurul = wordQuery("وړل", "verb"); const akheestul = wordQuery("اخیستل", "verb"); +const khandul = wordQuery("خندل", "verb"); const tests: { label: string; @@ -51,6 +52,11 @@ const tests: { output: [], error: true, }, + { + input: "تلم مې", + output: [], + error: true, + }, { input: "سړی زه ویني", output: [], @@ -1381,6 +1387,258 @@ const tests: { }, ], }, + { + label: "grammatically transitive", + cases: [ + { + input: "زه خاندم", + output: getPeople(1, "sing").map((person) => ({ + blocks: [ + { + key: 1, + block: makeSubjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(person), + }), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: T.Person.ThirdPlurMale, + }, + }, + ], + verb: { + type: "verb", + verb: khandul, + transitivity: "grammatically transitive", + canChangeTransitivity: false, + canChangeStatDyn: false, + negative: false, + tense: "presentVerb", + canChangeVoice: true, + isCompound: false, + voice: "active", + }, + externalComplement: undefined, + form: { + removeKing: false, + shrinkServant: false, + }, + })), + }, + { + input: "خاندم", + output: getPeople(1, "sing").map((person) => ({ + blocks: [ + { + key: 1, + block: makeSubjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(person), + }), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: T.Person.ThirdPlurMale, + }, + }, + ], + verb: { + type: "verb", + verb: khandul, + transitivity: "grammatically transitive", + canChangeTransitivity: false, + canChangeStatDyn: false, + negative: false, + tense: "presentVerb", + canChangeVoice: true, + isCompound: false, + voice: "active", + }, + externalComplement: undefined, + form: { + removeKing: true, + shrinkServant: false, + }, + })), + }, + { + input: "ما خندل", + output: getPeople(1, "sing").map((person) => ({ + blocks: [ + { + key: 1, + block: makeSubjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(person), + }), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: T.Person.ThirdPlurMale, + }, + }, + ], + verb: { + type: "verb", + verb: khandul, + transitivity: "grammatically transitive", + canChangeTransitivity: false, + canChangeStatDyn: false, + negative: false, + tense: "imperfectivePast", + canChangeVoice: true, + isCompound: false, + voice: "active", + }, + externalComplement: undefined, + form: { + removeKing: false, + shrinkServant: false, + }, + })), + }, + { + input: "خندل مې", + output: getPeople(1, "sing").map((person) => ({ + blocks: [ + { + key: 1, + block: makeSubjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(person), + }), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: T.Person.ThirdPlurMale, + }, + }, + ], + verb: { + type: "verb", + verb: khandul, + transitivity: "grammatically transitive", + canChangeTransitivity: false, + canChangeStatDyn: false, + negative: false, + tense: "imperfectivePast", + canChangeVoice: true, + isCompound: false, + voice: "active", + }, + externalComplement: undefined, + form: { + removeKing: false, + shrinkServant: true, + }, + })), + }, + { + input: "خندل", + output: [], + }, + { + input: "خاندم مې", + output: [], + error: true, + }, + { + input: "زه وینم", + output: getPeople(1, "sing").map((person) => ({ + blocks: [ + { + key: 1, + block: makeSubjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(person), + }), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: T.Person.ThirdPlurMale, + }, + }, + ], + verb: { + type: "verb", + verb: leedul, + transitivity: "grammatically transitive", + canChangeTransitivity: false, + canChangeStatDyn: false, + negative: false, + tense: "presentVerb", + canChangeVoice: true, + isCompound: false, + voice: "active", + }, + externalComplement: undefined, + form: { + removeKing: false, + shrinkServant: false, + }, + })), + }, + { + input: "ما ولیدل", + output: getPeople(1, "sing").flatMap((person) => + ( + ["transitive", "grammatically transitive"] as const + ).map((transitivity) => ({ + blocks: [ + { + key: 1, + block: makeSubjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(person), + }), + }, + { + key: 2, + block: + transitivity === "grammatically transitive" + ? { + type: "objectSelection", + selection: T.Person.ThirdPlurMale, + } + : makeObjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(T.Person.ThirdPlurMale), + }), + }, + ], + verb: { + type: "verb", + verb: leedul, + transitivity, + canChangeTransitivity: false, + canChangeStatDyn: false, + negative: false, + tense: "perfectivePast", + canChangeVoice: true, + isCompound: false, + voice: "active", + }, + externalComplement: undefined, + form: { + removeKing: transitivity === "transitive", + shrinkServant: false, + }, + })) + ), + }, + ], + }, ]; tests.forEach(({ label, cases }) => { @@ -1394,6 +1652,7 @@ tests.forEach(({ label, cases }) => { expect(parsed.map((p) => removeKeys(p.body))).toIncludeSameMembers( removeKeys(output) ); + expect(parsed.every((p) => p.errors.length === 0)).toBe(true); } }); }); diff --git a/src/lib/src/parsing/parse-vp.ts b/src/lib/src/parsing/parse-vp.ts index cee0c1b..b0e205e 100644 --- a/src/lib/src/parsing/parse-vp.ts +++ b/src/lib/src/parsing/parse-vp.ts @@ -55,6 +55,7 @@ export function parseVP( if (!verb || verb.type !== "VB" || verb.info.type !== "verb") { return []; } + // TODO: check for either VBE or Equative and VBP if ( !negativeInPlace({ neg: negIndex, @@ -67,7 +68,6 @@ export function parseVP( return []; } if (verb.info.aspect === "perfective") { - // TODO: check that the perfective head is in the right place and actually matches if (!ph) { return []; } @@ -77,11 +77,44 @@ export function parseVP( } } const tense = getTenseFromRootsStems(ba, verb.info.base, verb.info.aspect); - const isPast = isPastTense(tense); const transitivities = getTransitivities(verb.info.verb); - const results: T.ParseResult[] = []; - // eww... pretty imperative way of doing this... - for (let transitivity of transitivities) { + const nps = blocks.filter((x): x is T.ParsedNP => x.type === "NP"); + const miniPronouns = getMiniPronouns(kids); + return finishPossibleVPSs({ + tense, + transitivities, + nps, + miniPronouns, + tokens, + negative, + verb, + }); + }); +} + +function finishPossibleVPSs({ + tense, + transitivities, + nps, + miniPronouns, + negative, + verb, + tokens, +}: { + tense: T.VerbTense; + transitivities: T.Transitivity[]; + nps: T.ParsedNP[]; + miniPronouns: T.ParsedMiniPronoun[]; + tokens: Readonly; + negative: boolean; + verb: T.ParsedVBE; +}): T.ParseResult[] { + const isPast = isPastTense(tense); + return transitivities.flatMap>( + (transitivity): T.ParseResult[] => { + if (verb.info.type === "equative") { + return []; + } const v: T.VerbSelectionComplete = { type: "verb", verb: verb.info.verb, @@ -94,72 +127,516 @@ export function parseVP( isCompound: false, voice: "active", }; - - const nps = blocks.filter((x): x is T.ParsedNP => x.type === "NP"); if (transitivity === "intransitive") { - const errors: T.ParseError[] = []; - if (getMiniPronouns(kids).length) { - errors.push({ - message: "unknown mini-pronoun", - }); - } - if (nps.length > 1) { - continue; - } - if (nps.length === 0) { - const blocks: T.VPSBlockComplete[] = [ + return finishIntransitive({ + miniPronouns, + nps, + tokens, + v, + verbPerson: verb.person, + }); + } else if (transitivity === "transitive") { + return finishTransitive({ + miniPronouns, + nps, + tokens, + v, + verbPerson: verb.person, + isPast, + }); + } else { + return finishGrammaticallyTransitive({ + miniPronouns, + nps, + tokens, + v, + verbPerson: verb.person, + isPast, + }); + } + } + ); +} + +function finishIntransitive({ + miniPronouns, + nps, + tokens, + v, + verbPerson, +}: { + miniPronouns: T.ParsedMiniPronoun[]; + nps: T.ParsedNP[]; + tokens: Readonly; + v: T.VerbSelectionComplete; + verbPerson: T.Person; +}): T.ParseResult[] { + const errors: T.ParseError[] = []; + if (miniPronouns.length) { + errors.push({ + message: "unknown mini-pronoun", + }); + } + if (nps.length > 1) { + return []; + } + if (nps.length === 0) { + const blocks: T.VPSBlockComplete[] = [ + { + key: 1, + block: makeSubjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(verbPerson), + }), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: "none", + }, + }, + ]; + return [ + { + tokens, + body: { + blocks, + verb: v, + externalComplement: undefined, + form: { + removeKing: true, + shrinkServant: false, + }, + } as T.VPSelectionComplete, + errors, + }, + ]; + } + if (getPersonFromNP(nps[0].selection) !== verbPerson) { + errors.push({ message: "subject must agree with intransitive verb" }); + } + if (nps[0].inflected) { + errors.push({ + message: "subject of intransitive verb must not be inflected", + }); + } + const blocks: T.VPSBlockComplete[] = [ + { + key: 1, + block: makeSubjectSelectionComplete(nps[0].selection), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: "none", + }, + }, + ]; + return [ + { + tokens, + body: { + blocks, + verb: v, + externalComplement: undefined, + form: { + removeKing: false, + shrinkServant: false, + }, + } as T.VPSelectionComplete, + errors, + }, + ]; +} + +function finishTransitive({ + miniPronouns, + nps, + tokens, + v, + verbPerson, + isPast, +}: { + miniPronouns: T.ParsedMiniPronoun[]; + nps: T.ParsedNP[]; + tokens: Readonly; + v: T.VerbSelectionComplete; + verbPerson: T.Person; + isPast: boolean; +}): T.ParseResult[] { + // transitive + if (nps.length > 2) { + return []; + } + if (nps.length === 0) { + // present: + // - no king (subject) + // - servant (object) is shrunken + // past: + // - no king (object) + // - servant (subject) is shrunken + const errors: T.ParseError[] = []; + if (miniPronouns.length > 1) { + errors.push({ + message: "unknown mini-pronoun in kid's section", + }); + } + const blockOpts: T.VPSBlockComplete[][] = getPeopleFromMiniPronouns( + miniPronouns + ).map((person) => + !isPast + ? [ { key: 1, block: makeSubjectSelectionComplete({ type: "NP", - selection: makePronounSelection(verb.person), + selection: makePronounSelection(verbPerson), }), }, { key: 2, - block: { - type: "objectSelection", - selection: "none", - }, + block: makeObjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(person), + }), }, - ]; - results.push({ - tokens, - body: { - blocks, - verb: v, - externalComplement: undefined, - form: { - removeKing: true, - shrinkServant: false, - }, - } as T.VPSelectionComplete, - errors, - }); - continue; - } - if (getPersonFromNP(nps[0].selection) !== verb.person) { - errors.push({ message: "subject must agree with intransitive verb" }); - } - if (nps[0].inflected) { + ] + : [ + { + key: 1, + block: makeSubjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(person), + }), + }, + { + key: 2, + block: makeObjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(verbPerson), + }), + }, + ] + ); + return blockOpts.flatMap((blocks) => + returnParseResult( + tokens, + { + blocks, + verb: v, + externalComplement: undefined, + form: { + removeKing: true, + shrinkServant: true, + }, + } as T.VPSelectionComplete, + pronounConflictInBlocks(blocks) + ? [...errors, { message: "invalid subject/object combo" }] + : errors + ) + ); + } + if (nps.length === 1) { + const np = nps[0]; + // possibilities + // present: + // - no king (np is servant) + // - shrunken servant (np is king) + // past: + // - no king (np is servant) + // - shrunken servant (np is king) + return ( + [ + { + removeKing: true, + shrinkServant: false, + }, + { + removeKing: false, + shrinkServant: true, + }, + ] as const + ).flatMap>((form) => { + const errors: T.ParseError[] = []; + const king: T.NPSelection = form.removeKing + ? { + type: "NP", + selection: makePronounSelection(verbPerson), + } + : np.selection; + const servants: T.NPSelection[] = form.shrinkServant + ? getPeopleFromMiniPronouns(miniPronouns).map((person) => ({ + type: "NP", + selection: makePronounSelection(person), + })) + : [np.selection]; + // check for vp structure errors + if (form.removeKing) { + if (miniPronouns.length) { errors.push({ - message: "subject of intransitive verb must not be inflected", + message: "unknown mini-pronoun in kid's section", }); } - const blocks: T.VPSBlockComplete[] = [ + if (!isPast) { + if (isFirstOrSecondPersPronoun(np.selection)) + if (!np.inflected) { + errors.push({ + message: + "first or second pronoun object of non-past transitive verb must be inflected", + }); + } + } else { + if (!np.inflected) { + errors.push({ + message: + "object of non-past transitive verb must not be inflected", + }); + } + } + } else { + if (np.inflected) { + errors.push({ + message: !isPast + ? "object of a past tense transitive verb should not be inflected" + : "subject of a non-past tense transitive verb should not be inflected", + }); + } + if (getPersonFromNP(king) !== verbPerson) { + errors.push({ + message: `${ + isPast ? "past tense" : "non-past tense" + } transitive verb must agree agree with ${ + isPast ? "obect" : "subject" + }`, + }); + } + } + const blocksOps: T.VPSBlockComplete[][] = servants.map((servant) => + !isPast + ? [ + { + key: 1, + block: makeSubjectSelectionComplete(king), + }, + { + key: 2, + block: makeObjectSelectionComplete(servant), + }, + ] + : [ + { + key: 1, + block: makeSubjectSelectionComplete(servant), + }, + { + key: 2, + block: makeObjectSelectionComplete(king), + }, + ] + ); + return blocksOps.map((blocks) => ({ + tokens, + body: { + blocks, + verb: v, + externalComplement: undefined, + form, + } as T.VPSelectionComplete, + errors: pronounConflictInBlocks(blocks) + ? [...errors, { message: "invalid subject/object combo" }] + : errors, + })); + }); + } else { + if (isPast) { + return ( + [ + [nps[0], nps[1], false], + [nps[1], nps[0], true], + ] as const + ).flatMap(([s, o, flip]) => { + const errors: T.ParseError[] = []; + if ( + isInvalidSubjObjCombo( + getPersonFromNP(s.selection), + getPersonFromNP(o.selection) + ) + ) { + errors.push({ + message: "invalid subject/object combo", + }); + } + if (!s.inflected) { + errors.push({ + message: "subject of transitive past tense verb must be inflected", + }); + } + if (o.inflected) { + errors.push({ + message: + "object of past tense transitive verb must not be inflected", + }); + } + if (getPersonFromNP(o.selection) !== verbPerson) { + errors.push({ + message: "past tense transitive verb must agree with the object", + }); + } + let blocks: T.VPSBlockComplete[] = [ { key: 1, - block: makeSubjectSelectionComplete(nps[0].selection), + block: makeSubjectSelectionComplete(s.selection), }, { key: 2, - block: { - type: "objectSelection", - selection: "none", - }, + block: makeObjectSelectionComplete(o.selection), }, ]; - results.push({ + if (flip) { + blocks = blocks.reverse(); + } + return returnParseResult( + tokens, + { + blocks, + verb: v, + externalComplement: undefined, + form: { + removeKing: false, + shrinkServant: false, + }, + } as T.VPSelectionComplete, + errors + ); + }); + } else { + return ( + [ + [nps[0], nps[1], false], + [nps[1], nps[0], true], + ] as const + ).flatMap(([s, o, flip]) => { + const errors: T.ParseError[] = []; + if ( + isInvalidSubjObjCombo( + getPersonFromNP(s.selection), + getPersonFromNP(o.selection) + ) + ) { + errors.push({ + message: "invalid subject/object combo", + }); + } + if (isFirstOrSecondPersPronoun(o.selection)) { + if (!o.inflected) { + errors.push({ + message: + "object of transitive non-past tense verb must be inflected when it's a first or second person pronoun", + }); + } + } else { + if (o.inflected) { + errors.push({ + message: + "object of transitive non-past tense verb must not be inflected", + }); + } + } + if (s.inflected) { + errors.push({ + message: + "subject of transitive non-past tense verb must not be inflected", + }); + } + if (getPersonFromNP(s.selection) !== verbPerson) { + errors.push({ + message: + "non-past tense transitive verb must agree with the subject", + }); + } + let blocks: T.VPSBlockComplete[] = [ + { + key: 1, + block: makeSubjectSelectionComplete(s.selection), + }, + { + key: 2, + block: makeObjectSelectionComplete(o.selection), + }, + ]; + if (flip) { + blocks = blocks.reverse(); + } + return returnParseResult( + tokens, + { + blocks, + verb: v, + externalComplement: undefined, + form: { + removeKing: false, + shrinkServant: false, + }, + } as T.VPSelectionComplete, + errors + ); + }); + } + } +} + +function finishGrammaticallyTransitive({ + miniPronouns, + nps, + tokens, + v, + verbPerson, + isPast, +}: { + miniPronouns: T.ParsedMiniPronoun[]; + nps: T.ParsedNP[]; + tokens: Readonly; + v: T.VerbSelectionComplete; + verbPerson: T.Person; + isPast: boolean; +}): T.ParseResult[] { + const errors: T.ParseError[] = []; + if (isPast) { + if (nps.length === 1) { + if (miniPronouns.length) { + errors.push({ + message: "unknown mini-pronoun", + }); + } + if (verbPerson !== T.Person.ThirdPlurMale) { + errors.push({ + message: + "grammatically transitive verb must be 3rd pers. masc. plur.", + }); + } + if (isPast && !nps[0].inflected) { + errors.push({ + message: + "subject of past tense grammatically transitive verb must be inflected", + }); + } + const blocks: T.VPSBlockComplete[] = [ + { + key: 1, + block: makeSubjectSelectionComplete(nps[0].selection), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: T.Person.ThirdPlurMale, + }, + }, + ]; + return [ + { tokens, body: { blocks, @@ -171,444 +648,145 @@ export function parseVP( }, } as T.VPSelectionComplete, errors, + }, + ]; + } else if (nps.length === 0) { + if (miniPronouns.length > 1) { + errors.push({ + message: "unknown mini-pronoun", }); - continue; - } else if (transitivity === "transitive") { - // transitive - if (nps.length > 2) { - continue; - } - if (nps.length === 0) { - // present: - // - no king (subject) - // - servant (object) is shrunken - // past: - // - no king (object) - // - servant (subject) is shrunken - const errors: T.ParseError[] = []; - const miniPronouns = getMiniPronouns(kids); - if (miniPronouns.length > 1) { - errors.push({ - message: "unknown mini-pronoun in kid's section", - }); - } - const blockOpts: T.VPSBlockComplete[][] = getPeopleFromMiniPronouns( - miniPronouns - ).map((person) => - !isPast - ? [ - { - key: 1, - block: makeSubjectSelectionComplete({ - type: "NP", - selection: makePronounSelection(verb.person), - }), - }, - { - key: 2, - block: makeObjectSelectionComplete({ - type: "NP", - selection: makePronounSelection(person), - }), - }, - ] - : [ - { - key: 1, - block: makeSubjectSelectionComplete({ - type: "NP", - selection: makePronounSelection(person), - }), - }, - { - key: 2, - block: makeObjectSelectionComplete({ - type: "NP", - selection: makePronounSelection(verb.person), - }), - }, - ] - ); - const toAdd = blockOpts.flatMap((blocks) => - returnParseResult( - tokens, - { - blocks, - verb: v, - externalComplement: undefined, - form: { - removeKing: true, - shrinkServant: true, - }, - } as T.VPSelectionComplete, - pronounConflictInBlocks(blocks) - ? [...errors, { message: "invalid subject/object combo" }] - : errors - ) - ); - toAdd.forEach((r) => results.push(r)); - continue; - } - if (nps.length === 1) { - const np = nps[0]; - // possibilities - // present: - // - no king (np is servant) - // - shrunken servant (np is king) - // past: - // - no king (np is servant) - // - shrunken servant (np is king) - const res = ( - [ - { - removeKing: true, - shrinkServant: false, - }, - { - removeKing: false, - shrinkServant: true, - }, - ] as const - ).flatMap((form) => { - const errors: T.ParseError[] = []; - const king: T.NPSelection = form.removeKing - ? { - type: "NP", - selection: makePronounSelection(verb.person), - } - : np.selection; - const servants: T.NPSelection[] = form.shrinkServant - ? getPeopleFromMiniPronouns(kids).map((person) => ({ - type: "NP", - selection: makePronounSelection(person), - })) - : [np.selection]; - // check for vp structure errors - if (form.removeKing) { - if (getMiniPronouns(kids).length) { - errors.push({ - message: "unknown mini-pronoun in kid's section", - }); - } - if (!isPast) { - if (isFirstOrSecondPersPronoun(np.selection)) - if (!np.inflected) { - errors.push({ - message: - "first or second pronoun object of non-past transitive verb must be inflected", - }); - } - } else { - if (!np.inflected) { - errors.push({ - message: - "object of non-past transitive verb must not be inflected", - }); - } - } - } else { - if (np.inflected) { - errors.push({ - message: !isPast - ? "object of a past tense transitive verb should not be inflected" - : "subject of a non-past tense transitive verb should not be inflected", - }); - } - if (getPersonFromNP(king) !== verb.person) { - errors.push({ - message: `${ - isPast ? "past tense" : "non-past tense" - } transitive verb must agree agree with ${ - isPast ? "obect" : "subject" - }`, - }); - } - } - const blocksOps: T.VPSBlockComplete[][] = servants.map((servant) => - !isPast - ? [ - { - key: 1, - block: makeSubjectSelectionComplete(king), - }, - { - key: 2, - block: makeObjectSelectionComplete(servant), - }, - ] - : [ - { - key: 1, - block: makeSubjectSelectionComplete(servant), - }, - { - key: 2, - block: makeObjectSelectionComplete(king), - }, - ] - ); - return blocksOps.map((blocks) => ({ - tokens, - body: { - blocks, - verb: v, - externalComplement: undefined, - form, - } as T.VPSelectionComplete, - errors: pronounConflictInBlocks(blocks) - ? [...errors, { message: "invalid subject/object combo" }] - : errors, - })); - }); - res.forEach((r) => results.push(r)); - continue; - } else { - if (isPast) { - const res = ( - [ - [nps[0], nps[1], false], - [nps[1], nps[0], true], - ] as const - ).flatMap(([s, o, flip]) => { - const errors: T.ParseError[] = []; - if ( - isInvalidSubjObjCombo( - getPersonFromNP(s.selection), - getPersonFromNP(o.selection) - ) - ) { - errors.push({ - message: "invalid subject/object combo", - }); - } - if (!s.inflected) { - errors.push({ - message: - "subject of transitive past tense verb must be inflected", - }); - } - if (o.inflected) { - errors.push({ - message: - "object of past tense transitive verb must not be inflected", - }); - } - if (getPersonFromNP(o.selection) !== verb.person) { - errors.push({ - message: - "past tense transitive verb must agree with the object", - }); - } - let blocks: T.VPSBlockComplete[] = [ - { - key: 1, - block: makeSubjectSelectionComplete(s.selection), - }, - { - key: 2, - block: makeObjectSelectionComplete(o.selection), - }, - ]; - if (flip) { - blocks = blocks.reverse(); - } - return returnParseResult( - tokens, - { - blocks, - verb: v, - externalComplement: undefined, - form: { - removeKing: false, - shrinkServant: false, - }, - } as T.VPSelectionComplete, - errors - ); - }); - res.forEach((r) => results.push(r)); - continue; - } else { - const res = ( - [ - [nps[0], nps[1], false], - [nps[1], nps[0], true], - ] as const - ).flatMap(([s, o, flip]) => { - const errors: T.ParseError[] = []; - if ( - isInvalidSubjObjCombo( - getPersonFromNP(s.selection), - getPersonFromNP(o.selection) - ) - ) { - errors.push({ - message: "invalid subject/object combo", - }); - } - if (isFirstOrSecondPersPronoun(o.selection)) { - if (!o.inflected) { - errors.push({ - message: - "object of transitive non-past tense verb must be inflected when it's a first or second person pronoun", - }); - } - } else { - if (o.inflected) { - errors.push({ - message: - "object of transitive non-past tense verb must not be inflected", - }); - } - } - if (s.inflected) { - errors.push({ - message: - "subject of transitive non-past tense verb must not be inflected", - }); - } - if (getPersonFromNP(s.selection) !== verb.person) { - errors.push({ - message: - "non-past tense transitive verb must agree with the subject", - }); - } - let blocks: T.VPSBlockComplete[] = [ - { - key: 1, - block: makeSubjectSelectionComplete(s.selection), - }, - { - key: 2, - block: makeObjectSelectionComplete(o.selection), - }, - ]; - if (flip) { - blocks = blocks.reverse(); - } - return returnParseResult( - tokens, - { - blocks, - verb: v, - externalComplement: undefined, - form: { - removeKing: false, - shrinkServant: false, - }, - } as T.VPSelectionComplete, - errors - ); - }); - res.forEach((r) => results.push(r)); - continue; - } - } - } else { - // grammatically transitive - const errors: T.ParseError[] = []; - if (nps.length === 1) { - if (getMiniPronouns(kids).length) { - errors.push({ - message: "unknown mini-pronoun", - }); - } - if (verb.person !== T.Person.ThirdPlurMale) { - errors.push({ - message: - "grammatically transitive verb must be 3rd pers. masc. plur.", - }); - } - if (!nps[0].inflected) { - errors.push({ - message: - "subject of grammatically transitive verb must be inflected", - }); - } - const blocks: T.VPSBlockComplete[] = [ - { - key: 1, - block: makeSubjectSelectionComplete(nps[0].selection), - }, - { - key: 2, - block: { - type: "objectSelection", - selection: T.Person.ThirdPlurMale, - }, - }, - ]; - results.push({ - tokens, - body: { - blocks, - verb: v, - externalComplement: undefined, - form: { - removeKing: false, - shrinkServant: false, - }, - } as T.VPSelectionComplete, - errors, - }); - continue; - } else if (nps.length === 0) { - const miniPronouns = getMiniPronouns(kids); - if (miniPronouns.length > 1) { - errors.push({ - message: "unknown mini-pronoun", - }); - } - if (miniPronouns.length === 0) { - errors.push({ - message: "subject required for grammatically transitive verb", - }); - } - if (verb.person !== T.Person.ThirdPlurMale) { - errors.push({ - message: - "grammatically transitive verb must be 3rd pers. masc. plur.", - }); - } - getPeopleFromMiniPronouns(kids).forEach((person) => { - const blocks: T.VPSBlockComplete[] = [ - { - key: 1, - block: makeSubjectSelectionComplete({ - type: "NP", - selection: makePronounSelection(person), - }), - }, - { - key: 2, - block: { - type: "objectSelection", - selection: T.Person.ThirdPlurMale, - }, - }, - ]; - results.push({ - tokens, - body: { - blocks, - verb: v, - externalComplement: undefined, - form: { - removeKing: false, - shrinkServant: true, - }, - } as T.VPSelectionComplete, - errors, - }); - }); - continue; - } else { - continue; - } } + if (miniPronouns.length === 0) { + errors.push({ + message: "subject required for grammatically transitive verb", + }); + } + if (verbPerson !== T.Person.ThirdPlurMale) { + errors.push({ + message: + "grammatically transitive verb must be 3rd pers. masc. plur.", + }); + } + return getPeopleFromMiniPronouns(miniPronouns).map((person) => { + const blocks: T.VPSBlockComplete[] = [ + { + key: 1, + block: makeSubjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(person), + }), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: T.Person.ThirdPlurMale, + }, + }, + ]; + return { + tokens, + body: { + blocks, + verb: v, + externalComplement: undefined, + form: { + removeKing: false, + shrinkServant: true, + }, + } as T.VPSelectionComplete, + errors, + }; + }); } - return results; - }); + } else { + // non-past + if (miniPronouns.length) { + errors.push({ + message: "unknown mini-pronoun", + }); + } + if (nps.length === 1) { + const subj = nps[0]; + if (verbPerson !== getPersonFromNP(subj.selection)) { + errors.push({ + message: "non-past verb must agree with subject", + }); + } + if (nps[0].inflected) { + errors.push({ + message: + "subject of non-past tense grammatically transitive verb must not be inflected", + }); + } + const blocks: T.VPSBlockComplete[] = [ + { + key: 1, + block: makeSubjectSelectionComplete(nps[0].selection), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: T.Person.ThirdPlurMale, + }, + }, + ]; + return [ + { + tokens, + body: { + blocks, + verb: v, + externalComplement: undefined, + form: { + removeKing: false, + shrinkServant: false, + }, + } as T.VPSelectionComplete, + errors, + }, + ]; + } else if (nps.length === 0) { + if (miniPronouns.length > 1) { + errors.push({ + message: "unknown mini-pronoun", + }); + } + const blocks: T.VPSBlockComplete[] = [ + { + key: 1, + block: makeSubjectSelectionComplete({ + type: "NP", + selection: makePronounSelection(verbPerson), + }), + }, + { + key: 2, + block: { + type: "objectSelection", + selection: T.Person.ThirdPlurMale, + }, + }, + ]; + return [ + { + tokens, + body: { + blocks, + verb: v, + externalComplement: undefined, + form: { + removeKing: true, + shrinkServant: false, + }, + } as T.VPSelectionComplete, + errors, + }, + ]; + } + } + return []; } function getMiniPronouns(kids: T.ParsedKid[]): T.ParsedMiniPronoun[] { diff --git a/src/types.ts b/src/types.ts index 9aed801..bd48f37 100644 --- a/src/types.ts +++ b/src/types.ts @@ -726,9 +726,7 @@ export type EquativeTenseWithoutBa = | "subjunctive" | "habitual" | "past" - | "wouldBe" - | "pastSubjunctive" - | "wouldHaveBeen"; + | "pastSubjunctive"; export type PerfectTense = `${EquativeTense}Perfect`; export type AbilityTense = `${VerbTense}Modal`; export type ImperativeTense = `${Aspect}Imperative`; @@ -1196,7 +1194,12 @@ export type Block = { | VHead; }; -export type ParsedBlock = ParsedNP | ParsedPH | ParsedVBE | NegativeBlock; +export type ParsedBlock = + | ParsedNP + | ParsedPH + | ParsedVBE + | ParsedVBP + | NegativeBlock; export type ParsedKidsSection = { type: "kids"; @@ -1213,6 +1216,7 @@ export type ParsedPH = { s: string; }; export type ParsedVBE = Omit; +export type ParsedVBP = Omit; export type Kid = { key: number;