added event emitter for VPExplorer for monitoring events

This commit is contained in:
adueck 2024-05-05 17:47:48 +04:00
parent b5e33b1db9
commit 23f3dcd2c5
20 changed files with 1505 additions and 339 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "pashto-inflector",
"version": "7.0.9",
"version": "7.0.11",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pashto-inflector",
"version": "7.0.9",
"version": "7.0.11",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "pashto-inflector",
"version": "7.0.9",
"version": "7.0.11",
"author": "lingdocs.com",
"description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations",
"homepage": "https://verbs.lingdocs.com",

View File

@ -1,12 +1,12 @@
{
"name": "@lingdocs/ps-react",
"version": "7.0.9",
"version": "7.0.11",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@lingdocs/ps-react",
"version": "7.0.9",
"version": "7.0.11",
"license": "MIT",
"dependencies": {
"@formkit/auto-animate": "^1.0.0-beta.3",

View File

@ -1,6 +1,6 @@
{
"name": "@lingdocs/ps-react",
"version": "7.0.9",
"version": "7.0.11",
"description": "Pashto inflector library module with React components",
"main": "dist/components/library.js",
"module": "dist/components/library.js",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -10,7 +10,10 @@ import { completeVPSelection } from "../../../lib/src/phrase-building/vp-tools";
import VPExplorerQuiz from "./VPExplorerQuiz";
// @ts-ignore
import LZString from "lz-string";
import { vpsReducer } from "../../../lib/src/phrase-building/vps-reducer";
import {
VpsReducerAction,
vpsReducer,
} from "../../../lib/src/phrase-building/vps-reducer";
import { getObjectSelection } from "../../../lib/src/phrase-building/blocks-utils";
import VPPicker from "./VPPicker";
import AllTensesDisplay from "./AllTensesDisplay";
@ -34,6 +37,7 @@ function VPExplorer(props: {
handleLinkClick: ((ts: number) => void) | "none";
entryFeeder: T.EntryFeeder;
onlyPhrases?: boolean;
eventEmitter?: (e: string) => void;
}) {
const [vps, adjustVps] = useStickyReducer(
vpsReducer,
@ -85,7 +89,7 @@ function VPExplorer(props: {
const VPSFromUrl = getVPSFromUrl();
if (VPSFromUrl) {
setMode("phrases");
adjustVps({
eventWrapper(adjustVps)({
type: "load vps",
payload: VPSFromUrl,
});
@ -93,7 +97,15 @@ function VPExplorer(props: {
// eslint-disable-next-line
}, []);
function handleSubjObjSwap() {
adjustVps({ type: "swap subj/obj" });
eventWrapper(adjustVps)({ type: "swap subj/obj" });
}
function eventWrapper(f: (a: VpsReducerAction) => void) {
return function (action: VpsReducerAction) {
if (props.eventEmitter) {
props.eventEmitter(`VP exlorer ${props.verb.entry.p}`);
}
return f(action);
};
}
function quizLock<T>(f: T) {
if (mode === "quiz") {
@ -105,7 +117,7 @@ function VPExplorer(props: {
return f;
}
function handleSetForm(form: T.FormVersion) {
adjustVps({
eventWrapper(adjustVps)({
type: "set form",
payload: form,
});
@ -134,7 +146,7 @@ function VPExplorer(props: {
<div className="mt-3" style={{ maxWidth: "950px" }}>
<VerbPicker
vps={vps}
onChange={quizLock(adjustVps)}
onChange={quizLock(eventWrapper(adjustVps))}
opts={props.opts}
handleLinkClick={props.handleLinkClick}
/>
@ -190,13 +202,19 @@ function VPExplorer(props: {
<VPPicker
opts={props.opts}
entryFeeder={props.entryFeeder}
onChange={(payload) => adjustVps({ type: "load vps", payload })}
onChange={(payload) =>
eventWrapper(adjustVps)({ type: "load vps", payload })
}
vps={vps}
/>
)}
{mode !== "phrases" && (
<div className="my-2">
<TensePicker vps={vps} onChange={adjustVps} mode={mode} />
<TensePicker
vps={vps}
onChange={eventWrapper(adjustVps)}
mode={mode}
/>
</div>
)}
{mode === "phrases" && (
@ -207,7 +225,7 @@ function VPExplorer(props: {
object={object}
VS={vps.verb}
opts={props.opts}
onChange={adjustVps}
onChange={eventWrapper(adjustVps)}
/>
)}
{mode === "quiz" && <VPExplorerQuiz opts={props.opts} vps={vps} />}

View File

@ -5,199 +5,221 @@ import Phonetics from "../components/src/Phonetics";
import { getVerbInfo } from "../lib/src/verb-info";
import verbs from "../verbs";
import { useStickyState } from "../components/library";
import {
clamp
} from "../lib/src/p-text-helpers";
import {
randomNumber,
} from "../lib/src/misc-helpers";
import { clamp } from "../lib/src/p-text-helpers";
import { randomNumber } from "../lib/src/misc-helpers";
import { entryFeeder } from "./entryFeeder";
const transitivities: T.Transitivity[] = [
"transitive",
"intransitive",
"grammatically transitive",
"transitive",
"intransitive",
"grammatically transitive",
];
const allVerbs = verbs.map((v: { entry: T.DictionaryEntry, complement?: T.DictionaryEntry }) => ({
const allVerbs = verbs.map(
(v: { entry: T.DictionaryEntry; complement?: T.DictionaryEntry }) => ({
verb: v,
info: getVerbInfo(v.entry, v.complement),
}));
})
);
type VerbType = "simple" | "stative compound" | "dynamic compound";
const verbTypes: VerbType[] = [
"simple",
"stative compound",
"dynamic compound",
"simple",
"stative compound",
"dynamic compound",
];
function VPBuilderDemo({ opts }: {
opts: T.TextOptions,
}) {
const [verbTs, setVerbTs] = useStickyState<number>(0, "verbTs1");
const [verbTypeShowing, setVerbTypeShowing] = useStickyState<VerbType>("simple", "vTypeShowing");
const [transitivityShowing, setTransitivityShowing] = useStickyState<T.Transitivity>("intransitive", "transitivityShowing1");
// const onlyGrammTrans = (arr: Transitivity[]) => (
// arr.length === 1 && arr[0] === "grammatically transitive"
// );
// const ensureSimpleVerbTypeSelected = () => {
// if (!verbTypesShowing.includes["simple"]) {
// setVerbTypesShowing([...verbTypesShowing, "simple"]);
// }
// }
const handleVerbIndexChange = (e: any) => {
setVerbTs(parseInt(e.target.value));
function VPBuilderDemo({ opts }: { opts: T.TextOptions }) {
const [verbTs, setVerbTs] = useStickyState<number>(0, "verbTs1");
const [verbTypeShowing, setVerbTypeShowing] = useStickyState<VerbType>(
"simple",
"vTypeShowing"
);
const [transitivityShowing, setTransitivityShowing] =
useStickyState<T.Transitivity>("intransitive", "transitivityShowing1");
// const onlyGrammTrans = (arr: Transitivity[]) => (
// arr.length === 1 && arr[0] === "grammatically transitive"
// );
// const ensureSimpleVerbTypeSelected = () => {
// if (!verbTypesShowing.includes["simple"]) {
// setVerbTypesShowing([...verbTypesShowing, "simple"]);
// }
// }
const handleVerbIndexChange = (e: any) => {
setVerbTs(parseInt(e.target.value));
};
const handleTypeSelection = (e: any) => {
const type = e.target.value as VerbType;
if (type === "dynamic compound") {
setTransitivityShowing("transitive");
}
const handleTypeSelection = (e: any) => {
const type = e.target.value as VerbType;
if (type === "dynamic compound") {
setTransitivityShowing("transitive");
}
if (type === "stative compound" && transitivityShowing === "grammatically transitive") {
setTransitivityShowing("transitive");
}
setVerbTypeShowing(type);
if (
type === "stative compound" &&
transitivityShowing === "grammatically transitive"
) {
setTransitivityShowing("transitive");
}
const handleTransitivitySelection = (e: any) => {
const transitivity = e.target.value as T.Transitivity;
if (transitivity === "grammatically transitive") {
setVerbTypeShowing("simple");
}
if (transitivity === "intransitive" && verbTypeShowing === "dynamic compound") {
setTransitivityShowing("transitive");
return;
}
setTransitivityShowing(e.target.value as T.Transitivity);
setVerbTypeShowing(type);
};
const handleTransitivitySelection = (e: any) => {
const transitivity = e.target.value as T.Transitivity;
if (transitivity === "grammatically transitive") {
setVerbTypeShowing("simple");
}
const verbsAvailable = allVerbs.filter((verb) => (
(
(verb.info.type === "transitive or grammatically transitive simple" && verbTypeShowing === "simple") && (transitivityShowing === "transitive" || transitivityShowing === "grammatically transitive")
) ||
((
verbTypeShowing === verb.info.type ||
(verbTypeShowing === "stative compound" && verb.info.type === "dynamic or stative compound") ||
(verbTypeShowing === "dynamic compound" && verb.info.type === "dynamic or stative compound") ||
(verbTypeShowing === "dynamic compound" && verb.info.type === "dynamic or generative stative compound") ||
(verbTypeShowing === "stative compound" && verb.info.type === "dynamic or generative stative compound")
)
&& (
transitivityShowing === verb.info.transitivity
))
)).sort((a, b) => a.verb.entry.p.localeCompare(b.verb.entry.p, "ps"));
if (
transitivity === "intransitive" &&
verbTypeShowing === "dynamic compound"
) {
setTransitivityShowing("transitive");
return;
}
setTransitivityShowing(e.target.value as T.Transitivity);
};
const verbsAvailable = allVerbs
.filter(
(verb) =>
(verb.info.type === "transitive or grammatically transitive simple" &&
verbTypeShowing === "simple" &&
(transitivityShowing === "transitive" ||
transitivityShowing === "grammatically transitive")) ||
((verbTypeShowing === verb.info.type ||
(verbTypeShowing === "stative compound" &&
verb.info.type === "dynamic or stative compound") ||
(verbTypeShowing === "dynamic compound" &&
verb.info.type === "dynamic or stative compound") ||
(verbTypeShowing === "dynamic compound" &&
verb.info.type === "dynamic or generative stative compound") ||
(verbTypeShowing === "stative compound" &&
verb.info.type === "dynamic or generative stative compound")) &&
transitivityShowing === verb.info.transitivity)
)
.sort((a, b) => a.verb.entry.p.localeCompare(b.verb.entry.p, "ps"));
const v = (() => {
const vFound = verbsAvailable.find(v => v.verb.entry.ts === verbTs);
if (vFound) return vFound;
if (verbsAvailable.length === 0) return undefined;
const vTopOfList = verbsAvailable[0];
setVerbTs(vTopOfList.verb.entry.ts);
return vTopOfList;
})();
const v = (() => {
const vFound = verbsAvailable.find((v) => v.verb.entry.ts === verbTs);
if (vFound) return vFound;
if (verbsAvailable.length === 0) return undefined;
const vTopOfList = verbsAvailable[0];
setVerbTs(vTopOfList.verb.entry.ts);
return vTopOfList;
})();
const pickRandomVerb = () => {
let newIndex: number;
do {
newIndex = randomNumber(0, verbsAvailable.length);
} while(verbsAvailable[newIndex].verb.entry.ts === verbTs);
setVerbTs(verbsAvailable[newIndex].verb.entry.ts);
};
const makeVerbLabel = (entry: T.DictionaryEntry): string => (
`${entry.p} - ${clamp(entry.e, 20)}`
);
// const rs = v ? getAllRs(v.verb as T.VerbEntry) : undefined
return <div className="mt-4">
<div className="d-block mx-auto card" style={{ maxWidth: "700px", background: "var(--closer)"}}>
<div className="card-body">
<div className="row">
<div className="col-sm-6">
{v ?
<div>
<div className="mb-1">Select a verb:</div>
<div className="input-group">
<select className="custom-select" value={verbTs} onChange={handleVerbIndexChange}>
{verbsAvailable.length
? verbsAvailable.map((v, i) => (
<option value={v.verb.entry.ts} key={i} dir="ltr">
{"\u200E"}{makeVerbLabel(v.verb.entry)}
</option>
))
: <option>Select a verb type</option>
}
</select>
<div className="input-group-append">
<button className="btn btn-secondary" onClick={pickRandomVerb}>
<i className="fas fa-random" />
</button>
</div>
</div>
<div className="my-3">
<div>
<strong>
<Pashto opts={opts}>{v.verb.entry}</Pashto>
{` `}-{` `}
<Phonetics opts={opts}>{v.verb.entry}</Phonetics>
</strong>
{` `}
<em>{v.verb.entry.c}</em>
</div>
<div className="ml-3">{v.verb.entry.e}</div>
</div>
</div>
: <div className="alert alert-warning mb-4" role="alert">
No such verbs available...
</div>
}
const pickRandomVerb = () => {
let newIndex: number;
do {
newIndex = randomNumber(0, verbsAvailable.length);
} while (verbsAvailable[newIndex].verb.entry.ts === verbTs);
setVerbTs(verbsAvailable[newIndex].verb.entry.ts);
};
const makeVerbLabel = (entry: T.DictionaryEntry): string =>
`${entry.p} - ${clamp(entry.e, 20)}`;
// const rs = v ? getAllRs(v.verb as T.VerbEntry) : undefined
return (
<div className="mt-4">
<div
className="d-block mx-auto card"
style={{ maxWidth: "700px", background: "var(--closer)" }}
>
<div className="card-body">
<div className="row">
<div className="col-sm-6">
{v ? (
<div>
<div className="mb-1">Select a verb:</div>
<div className="input-group">
<select
className="custom-select"
value={verbTs}
onChange={handleVerbIndexChange}
>
{verbsAvailable.length ? (
verbsAvailable.map((v, i) => (
<option value={v.verb.entry.ts} key={i} dir="ltr">
{"\u200E"}
{makeVerbLabel(v.verb.entry)}
</option>
))
) : (
<option>Select a verb type</option>
)}
</select>
<div className="input-group-append">
<button
className="btn btn-secondary"
onClick={pickRandomVerb}
>
<i className="fas fa-random" />
</button>
</div>
<div className="col-sm-6">
<h6>Verb type:</h6>
<div onChange={handleTypeSelection}>
{verbTypes.map((type) => (
<div key={type} className="form-check">
<input
className="form-check-input"
type="radio"
name="verb-type"
checked={verbTypeShowing === type}
value={type}
onChange={() => null}
/>
<label className="form-check-label">
{type}
</label>
</div>
))}
</div>
<h6 className="mt-2">Transitivity:</h6>
<div onChange={handleTransitivitySelection}>
{transitivities.map((transitivity) => (
<div key={transitivity} className="form-check">
<input
className="form-check-input"
type="radio"
name="transitivity"
checked={transitivityShowing === transitivity}
onChange={() => null}
value={transitivity}
/>
<label className="form-check-label">
{transitivity}
</label>
</div>
))}
</div>
</div>
<div className="my-3">
<div>
<strong>
<Pashto opts={opts}>{v.verb.entry}</Pashto>
{` `}-{` `}
<Phonetics opts={opts}>{v.verb.entry}</Phonetics>
</strong>
{` `}
<em>{v.verb.entry.c}</em>
</div>
<div className="ml-3">{v.verb.entry.e}</div>
</div>
</div>
) : (
<div className="alert alert-warning mb-4" role="alert">
No such verbs available...
</div>
)}
</div>
<div className="col-sm-6">
<h6>Verb type:</h6>
<div onChange={handleTypeSelection}>
{verbTypes.map((type) => (
<div key={type} className="form-check">
<input
className="form-check-input"
type="radio"
name="verb-type"
checked={verbTypeShowing === type}
value={type}
onChange={() => null}
/>
<label className="form-check-label">{type}</label>
</div>
))}
</div>
<h6 className="mt-2">Transitivity:</h6>
<div onChange={handleTransitivitySelection}>
{transitivities.map((transitivity) => (
<div key={transitivity} className="form-check">
<input
className="form-check-input"
type="radio"
name="transitivity"
checked={transitivityShowing === transitivity}
onChange={() => null}
value={transitivity}
/>
<label className="form-check-label">{transitivity}</label>
</div>
))}
</div>
</div>
</div>
</div>
{v?.verb.entry && <div style={{ paddingBottom: "20px" }}>
<PhraseBuilder
handleLinkClick="none"
verb={v.verb as T.VerbEntry}
entryFeeder={entryFeeder}
opts={opts}
/>
</div>}
</div>;
</div>
{v?.verb.entry && (
<div style={{ paddingBottom: "20px" }}>
<PhraseBuilder
handleLinkClick="none"
verb={v.verb as T.VerbEntry}
entryFeeder={entryFeeder}
opts={opts}
/>
</div>
)}
</div>
);
}
export default VPBuilderDemo;
export default VPBuilderDemo;

View File

@ -1,6 +1,6 @@
{
"name": "@lingdocs/inflect",
"version": "7.0.9",
"version": "7.0.11",
"description": "Pashto inflector library",
"main": "dist/index.js",
"types": "dist/lib/library.d.ts",

View File

@ -4,9 +4,9 @@ import { parseEquative } from "./parse-equative";
import { parseKidsSection } from "./parse-kids-section";
import { parseNeg } from "./parse-negative";
import { parseNPAP } from "./parse-npap";
import { parsePastPart } from "./parse-past-part";
import { parseVBP } from "./parse-vbp";
import { parsePH } from "./parse-ph";
import { parseVerb } from "./parse-verb";
import { parseVBE } from "./parse-vbe";
import {
bindParseResult,
returnParseResult,
@ -14,6 +14,7 @@ import {
isParsedVBP,
startsVerbSection,
} from "./utils";
import { isKedulStatEntry } from "./parse-verb-helpers";
export function parseBlocks(
tokens: Readonly<T.Token[]>,
@ -32,14 +33,15 @@ export function parseBlocks(
(b): b is T.ParsedPH => b.type === "PH"
);
// TOOD: rather parse VBP / VBE
const allBlocks: T.ParseResult<T.ParsedBlock | T.ParsedKidsSection>[] = [
...(!inVerbSection ? parseNPAP(tokens, lookup) : []),
// ensure at most one of each PH, VBE, VBP
...(prevPh ? [] : parsePH(tokens)),
...(blocks.some(isParsedVBE)
? []
: [...parseVerb(tokens, lookup), ...parseEquative(tokens)]),
...(blocks.some(isParsedVBP) ? [] : parsePastPart(tokens, lookup)),
: [...parseVBE(tokens, lookup), ...parseEquative(tokens)]),
...(blocks.some(isParsedVBP) ? [] : parseVBP(tokens, lookup)),
...(blocks.some((b) => b.type === "negative") ? [] : parseNeg(tokens)),
...parseKidsSection(tokens, []),
];
@ -82,12 +84,24 @@ function phMatches(
if (!ph) {
return true;
}
if (!vb) {
return true;
}
if (vb.info.type !== "verb") {
return false;
}
if (["را", "در", "ور"].includes(ph?.s)) {
if (
isKedulStatEntry(vb.info.verb.entry) &&
vb.info.base === "stem" &&
vb.info.aspect === "perfective"
) {
return true;
}
// TODO: handle را غل etc! ? or is
return false;
}
const verbPh = getPhFromVerb(vb.info.verb, vb.info.base);
return verbPh === ph.s;
}

View File

@ -0,0 +1,245 @@
/* eslint-disable jest/valid-title */
import * as T from "../../../types";
import { parseKedul } from "./parse-kedul";
import { kedulDyn, kedulStat } from "./irreg-verbs";
import { tokenizer } from "./tokenizer";
import { getPeople, removeKeys } from "./utils";
const tests: {
label: string;
cases: {
input: string;
output: T.ParsedVBE[];
}[];
}[] = [
{
label: "keG-",
cases: [
{
input: "کېږې",
output: getPeople(2, "sing").flatMap<T.ParsedVBE>((person) => [
{
type: "VB",
info: {
aspect: "imperfective",
base: "stem",
type: "verb",
verb: kedulStat,
},
person,
},
{
type: "VB",
info: {
aspect: "imperfective",
base: "stem",
type: "verb",
verb: kedulDyn,
},
person,
},
]),
},
],
},
{
label: "ked-",
cases: [
{
input: "کېدم",
output: getPeople(1, "sing").flatMap<T.ParsedVBE>((person) => [
{
type: "VB",
info: {
aspect: "imperfective",
base: "root",
type: "verb",
verb: kedulStat,
},
person,
},
{
type: "VB",
info: {
aspect: "imperfective",
base: "root",
type: "verb",
verb: kedulDyn,
},
person,
},
]),
},
{
input: "کېدل",
output: [
{
type: "VB",
info: {
aspect: "imperfective",
base: "root",
type: "verb",
verb: kedulStat,
},
person: T.Person.ThirdPlurMale,
},
{
type: "VB",
info: {
aspect: "imperfective",
base: "root",
type: "verb",
verb: kedulDyn,
},
person: T.Person.ThirdPlurMale,
},
],
},
{
input: "کېدلل",
output: [],
},
],
},
{
label: "sh-",
cases: [
{
input: "شې",
output: getPeople(2, "sing").flatMap<T.ParsedVBE>((person) => [
{
type: "VB",
info: {
aspect: "perfective",
base: "stem",
type: "verb",
verb: kedulStat,
},
person,
},
{
type: "VB",
info: {
aspect: "perfective",
base: "stem",
type: "verb",
verb: kedulDyn,
},
person,
},
]),
},
],
},
{
label: "shw-",
cases: [
{
input: "شوم",
output: getPeople(1, "sing").flatMap<T.ParsedVBE>((person) => [
{
type: "VB",
info: {
aspect: "perfective",
base: "root",
type: "verb",
verb: kedulStat,
},
person,
},
{
type: "VB",
info: {
aspect: "perfective",
base: "root",
type: "verb",
verb: kedulDyn,
},
person,
},
]),
},
{
input: "شول",
output: [
{
type: "VB",
info: {
aspect: "perfective",
base: "root",
type: "verb",
verb: kedulStat,
},
person: T.Person.ThirdPlurMale,
},
{
type: "VB",
info: {
aspect: "perfective",
base: "root",
type: "verb",
verb: kedulDyn,
},
person: T.Person.ThirdPlurMale,
},
],
},
{
input: "شولل",
output: [],
},
],
},
{
label: "sho",
cases: [
{
input: "شو",
output: [
...[
T.Person.ThirdSingMale,
T.Person.FirstPlurMale,
T.Person.FirstPlurFemale,
].flatMap((person) =>
[kedulStat, kedulDyn].map<T.ParsedVBE>((verb) => ({
type: "VB",
info: {
aspect: "perfective",
base: "root",
type: "verb",
verb,
},
person,
}))
),
...[T.Person.FirstPlurMale, T.Person.FirstPlurFemale].flatMap(
(person) =>
[kedulStat, kedulDyn].map<T.ParsedVBE>((verb) => ({
type: "VB",
info: {
aspect: "perfective",
base: "stem",
type: "verb",
verb,
},
person,
}))
),
],
},
],
},
];
tests.forEach(({ label, cases }) => {
test(label, () => {
cases.forEach(({ input, output }) => {
const tokens = tokenizer(input);
const vbe = parseKedul(tokens);
expect(vbe.every((v) => v.errors.length === 0)).toBe(true);
expect(removeKeys(vbe.map((v) => v.body))).toIncludeSameMembers(
removeKeys(output)
);
});
});
});

View File

@ -0,0 +1,154 @@
import * as T from "../../../types";
import { getVerbEnding } from "./parse-verb-helpers";
import { kedulDyn, kedulStat } from "./irreg-verbs";
import { returnParseResults } from "./utils";
export function parseKedul(
tokens: Readonly<T.Token[]>
): T.ParseResult<T.ParsedVBE>[] {
const [first, ...rest] = tokens;
const start = first.s.slice(0, -1);
const ending = first.s.at(-1) || "";
const people = getVerbEnding(ending);
if (first.s === "شو") {
return returnParseResults<T.ParsedVBE>(rest, [
...[
T.Person.ThirdSingMale,
T.Person.FirstPlurMale,
T.Person.FirstPlurFemale,
].flatMap((person) =>
[kedulStat, kedulDyn].map<T.ParsedVBE>((verb) => ({
type: "VB",
info: {
aspect: "perfective",
base: "root",
type: "verb",
verb,
},
person,
}))
),
...[T.Person.FirstPlurMale, T.Person.FirstPlurFemale].flatMap((person) =>
[kedulStat, kedulDyn].map<T.ParsedVBE>((verb) => ({
type: "VB",
info: {
aspect: "perfective",
base: "stem",
type: "verb",
verb,
},
person,
}))
),
]);
}
if (start === "کېږ") {
return returnParseResults<T.ParsedVBE>(
rest,
people.stem.flatMap<T.ParsedVBE>((person) => [
{
type: "VB",
info: {
aspect: "imperfective",
base: "stem",
type: "verb",
verb: kedulStat,
},
person,
},
{
type: "VB",
info: {
aspect: "imperfective",
base: "stem",
type: "verb",
verb: kedulDyn,
},
person,
},
])
);
}
if (start === "کېد" || (start === "کېدل" && ending !== "ل")) {
return returnParseResults<T.ParsedVBE>(
rest,
people.root.flatMap<T.ParsedVBE>((person) => [
{
type: "VB",
info: {
aspect: "imperfective",
base: "root",
type: "verb",
verb: kedulStat,
},
person,
},
{
type: "VB",
info: {
aspect: "imperfective",
base: "root",
type: "verb",
verb: kedulDyn,
},
person,
},
])
);
}
if (start === "ش") {
return returnParseResults<T.ParsedVBE>(
rest,
people.stem.flatMap<T.ParsedVBE>((person) => [
{
type: "VB",
info: {
aspect: "perfective",
base: "stem",
type: "verb",
verb: kedulStat,
},
person,
},
{
type: "VB",
info: {
aspect: "perfective",
base: "stem",
type: "verb",
verb: kedulDyn,
},
person,
},
])
);
}
if (start === "شو" || (start === "شول" && ending !== "ل")) {
return returnParseResults<T.ParsedVBE>(
rest,
people.root.flatMap<T.ParsedVBE>((person) => [
{
type: "VB",
info: {
aspect: "perfective",
base: "root",
type: "verb",
verb: kedulStat,
},
person,
},
{
type: "VB",
info: {
aspect: "perfective",
base: "root",
type: "verb",
verb: kedulDyn,
},
person,
},
])
);
}
return [];
}

View File

@ -9,7 +9,7 @@ import {
raatlul,
} from "./irreg-verbs";
import { lookup, wordQuery } from "./lookup";
import { parseVerb } from "./parse-verb";
import { parseVBE } from "./parse-vbe";
import { tokenizer } from "./tokenizer";
import { getPeople, removeKeys } from "./utils";
@ -935,9 +935,6 @@ const tests: {
},
],
},
// TODO: It would probably be more effecient just to return the kedul verb options
// and then when we put things together with the perfective head parsed they could
// become raatlul etc...
{
input: "شي",
output: [
@ -965,42 +962,6 @@ const tests: {
},
verb: kedulStat,
},
{
stem: {
persons: [
T.Person.ThirdSingMale,
T.Person.ThirdSingFemale,
T.Person.ThirdPlurMale,
T.Person.ThirdPlurFemale,
],
aspects: ["perfective"],
},
verb: raatlul,
},
{
stem: {
persons: [
T.Person.ThirdSingMale,
T.Person.ThirdSingFemale,
T.Person.ThirdPlurMale,
T.Person.ThirdPlurFemale,
],
aspects: ["perfective"],
},
verb: dartlul,
},
{
stem: {
persons: [
T.Person.ThirdSingMale,
T.Person.ThirdSingFemale,
T.Person.ThirdPlurMale,
T.Person.ThirdPlurFemale,
],
aspects: ["perfective"],
},
verb: wartlul,
},
],
},
],
@ -1011,7 +972,7 @@ tests.forEach(({ label, cases }) => {
test(label, () => {
cases.forEach(({ input, output }) => {
const tokens = tokenizer(input);
const vbs = parseVerb(tokens, lookup).map((r) => r.body);
const vbs = parseVBE(tokens, lookup).map((r) => r.body);
const madeVbsS = output.reduce<T.ParsedVBE[]>((acc, o) => {
return [
...acc,

View File

@ -1,26 +1,22 @@
import * as T from "../../../types";
import { removeFVarientsFromVerb } from "../accent-and-ps-utils";
import { isInVarients, lastVowelNotA } from "../p-text-helpers";
import {
dartlul,
kedulDyn,
kedulStat,
raatlul,
tlul,
wartlul,
} from "./irreg-verbs";
import { dartlul, raatlul, tlul, wartlul } from "./irreg-verbs";
import { LookupFunction } from "./lookup";
import { shortVerbEndConsonant } from "./misc";
import { parseKedul } from "./parse-kedul";
import { getVerbEnding } from "./parse-verb-helpers";
// TODO: کول verbs!
// check that aawu stuff is working
// check oo`azmooy -
// check څاته
// laaRa shum etc
// TODO: proper use of perfective with sh
// TODO: use of raa, dar, war with sh
// TODO: هغه لاړ
export function parseVerb(
export function parseVBE(
tokens: Readonly<T.Token[]>,
lookup: LookupFunction
): T.ParseResult<T.ParsedVBE>[] {
@ -36,6 +32,7 @@ export function parseVerb(
errors: [],
}));
}
const kedulStat = parseKedul(tokens);
const ending = first.s.at(-1) || "";
const people = getVerbEnding(ending);
const imperativePeople = getImperativeVerbEnding(ending);
@ -46,11 +43,14 @@ export function parseVerb(
// console.log({ verbs: JSON.stringify(verbs) });
// }
// Then find out which ones match exactly and how
return matchVerbs(first.s, verbs, people, imperativePeople).map((body) => ({
tokens: rest,
body,
errors: [],
}));
return [
...kedulStat,
...matchVerbs(first.s, verbs, people, imperativePeople).map((body) => ({
tokens: rest,
body,
errors: [],
})),
];
}
function matchVerbs(
@ -65,6 +65,9 @@ function matchVerbs(
const w: T.ParsedVBE[] = [];
const lEnding = s.endsWith("ل");
const base = s.endsWith("ل") ? s : s.slice(0, -1);
if (["کېږ", "کېد", "ش", "شو", "شول"].includes(base)) {
return [];
}
const matchShortOrLong = (b: string, x: string) => {
return b === x || (!lEnding && b === x.slice(0, -1));
};
@ -317,61 +320,6 @@ function getImperativeVerbEnding(e: string): T.Person[] {
return [];
}
function getVerbEnding(e: string): {
stem: T.Person[];
root: T.Person[];
} {
if (e === "م") {
return {
root: [T.Person.FirstSingMale, T.Person.FirstSingFemale],
stem: [T.Person.FirstSingMale, T.Person.FirstSingFemale],
};
} else if (e === "ې") {
return {
root: [
T.Person.SecondSingMale,
T.Person.SecondSingFemale,
T.Person.ThirdPlurFemale,
],
stem: [T.Person.SecondSingMale, T.Person.SecondSingFemale],
};
} else if (e === "ي") {
return {
stem: [
T.Person.ThirdSingMale,
T.Person.ThirdSingFemale,
T.Person.ThirdPlurMale,
T.Person.ThirdPlurFemale,
],
root: [],
};
} else if (e === "و") {
return {
root: [T.Person.FirstPlurMale, T.Person.FirstPlurFemale],
stem: [T.Person.FirstPlurMale, T.Person.FirstPlurFemale],
};
} else if (e === "ئ") {
return {
root: [T.Person.SecondPlurMale, T.Person.SecondPlurFemale],
stem: [T.Person.SecondPlurMale, T.Person.SecondPlurFemale],
};
} else if (e === "ه") {
return {
root: [T.Person.ThirdSingFemale],
stem: [],
};
} else if (e === "ل") {
return {
root: [T.Person.ThirdPlurMale],
stem: [],
};
}
return {
root: [],
stem: [],
};
}
// TODO: could handle all sh- verbs for efficiencies sake
function parseIrregularVerb(s: string): T.ParsedVBE[] {
if (["ته", "راته", "ورته", "درته"].includes(s)) {
@ -394,38 +342,6 @@ function parseIrregularVerb(s: string): T.ParsedVBE[] {
},
];
}
if (s === "شو") {
return [
...[
T.Person.ThirdSingMale,
T.Person.FirstPlurMale,
T.Person.FirstPlurFemale,
].flatMap((person) =>
[kedulStat, kedulDyn].map<T.ParsedVBE>((verb) => ({
type: "VB",
info: {
aspect: "perfective",
base: "root",
type: "verb",
verb,
},
person,
}))
),
...[T.Person.FirstPlurMale, T.Person.FirstPlurFemale].flatMap((person) =>
[kedulStat, kedulDyn].map<T.ParsedVBE>((verb) => ({
type: "VB",
info: {
aspect: "perfective",
base: "stem",
type: "verb",
verb,
},
person,
}))
),
];
}
return [];
}

View File

@ -1,7 +1,7 @@
import * as T from "../../../types";
import { lookup, wordQuery } from "./lookup";
import { tokenizer } from "./tokenizer";
import { parsePastPart } from "./parse-past-part";
import { parseVBP } from "./parse-vbp";
import { kawulDyn, kawulStat, kedulDyn, kedulStat } from "./irreg-verbs";
const leedul = wordQuery("لیدل", "verb");
@ -178,7 +178,7 @@ describe("parsing past participles", () => {
test(label, () => {
cases.forEach(({ input, output }) => {
const tokens = tokenizer(input);
const res = parsePastPart(tokens, lookup).map(({ body }) => body);
const res = parseVBP(tokens, lookup).map(({ body }) => body);
expect(res).toEqual(output);
});
});

View File

@ -2,13 +2,23 @@ import * as T from "../../../types";
import { LookupFunction } from "./lookup";
import { returnParseResult } from "./utils";
export function parsePastPart(
export function parseVBP(
tokens: Readonly<T.Token[]>,
lookup: LookupFunction
): T.ParseResult<T.ParsedVBP>[] {
if (tokens.length === 0) {
return [];
}
return [
...parsePastPart(tokens, lookup),
// ...parseAbility(tokens),
];
}
function parsePastPart(
tokens: Readonly<T.Token[]>,
lookup: LookupFunction
): T.ParseResult<T.ParsedVBP>[] {
const [{ s }, ...rest] = tokens;
const ending: "ی" | "ي" | "ې" = s.at(-1) as "ی" | "ي" | "ې";
if (!ending || !["ی", "ي", "ې"].includes(ending)) {
@ -32,6 +42,34 @@ export function parsePastPart(
.flatMap((m) => returnParseResult(rest, m));
}
// function parseAbility(
// tokens: Readonly<T.Token[]>,
// lookup: LookupFunction
// ): T.ParseResult<T.ParsedVBP>[] {
// // TODO: keday
// if (tokens.length === 0) {
// return [];
// }
// const [{ s }, ...rest] = tokens;
// const start = s.endsWith("ای")
// ? s.slice(0, -2)
// : s.endsWith("ی")
// ? s.slice(0, 1)
// : "";
// if (!start) return [];
// const matches = lookup(start, "ability");
// return matches
// .map<T.ParsedVBP>((verb) => ({
// type: "VB",
// info: {
// type: "ability",
// verb,
// aspect: "perfective", // TODO GET ASPECT!!
// },
// }))
// .flatMap((m) => returnParseResult(rest, m));
// }
function endingGenderNum(ending: "ی" | "ي" | "ې"): T.GenderNumber[] {
if (ending === "ی") {
return [

View File

@ -0,0 +1,60 @@
import * as T from "../../../types";
export function isKedulStatEntry(v: T.VerbDictionaryEntry): boolean {
return v.p === "کېدل" && v.e === "to become _____";
}
export function getVerbEnding(e: string): {
stem: T.Person[];
root: T.Person[];
} {
if (e === "م") {
return {
root: [T.Person.FirstSingMale, T.Person.FirstSingFemale],
stem: [T.Person.FirstSingMale, T.Person.FirstSingFemale],
};
} else if (e === "ې") {
return {
root: [
T.Person.SecondSingMale,
T.Person.SecondSingFemale,
T.Person.ThirdPlurFemale,
],
stem: [T.Person.SecondSingMale, T.Person.SecondSingFemale],
};
} else if (e === "ي") {
return {
stem: [
T.Person.ThirdSingMale,
T.Person.ThirdSingFemale,
T.Person.ThirdPlurMale,
T.Person.ThirdPlurFemale,
],
root: [],
};
} else if (e === "و") {
return {
root: [T.Person.FirstPlurMale, T.Person.FirstPlurFemale],
stem: [T.Person.FirstPlurMale, T.Person.FirstPlurFemale],
};
} else if (e === "ئ") {
return {
root: [T.Person.SecondPlurMale, T.Person.SecondPlurFemale],
stem: [T.Person.SecondPlurMale, T.Person.SecondPlurFemale],
};
} else if (e === "ه") {
return {
root: [T.Person.ThirdSingFemale],
stem: [],
};
} else if (e === "ل") {
return {
root: [T.Person.ThirdPlurMale],
stem: [],
};
}
return {
root: [],
stem: [],
};
}

View File

@ -104,6 +104,7 @@ function getTenses(
}
if (verb.info.type === "verb") {
if (verb.info.aspect === "perfective") {
// TODO: handle kedul sh perfective
if (!ph) {
return [];
}

View File

@ -62,6 +62,18 @@ export function bindParseResult<C, D>(
return cleanOutResults(nextPossibilities);
}
export function returnParseResults<D>(
tokens: Readonly<T.Token[]>,
body: D[],
errors?: T.ParseError[]
): T.ParseResult<D>[] {
return body.map<T.ParseResult<D>>((b) => ({
tokens,
body: b,
errors: errors || [],
}));
}
export function returnParseResultS<D>(
tokens: Readonly<T.Token[]>,
body: D,

View File

@ -34,6 +34,8 @@ type BlankoutOptions = {
predicate?: boolean;
};
// TODO: Should there be a way to get length options on compiled VP ?
// function compilePs({ blocks, kids, verb: { head, rest }, VP }: CompilePsInput): T.SingleOrLengthOpts<T.PsString[]> {
// if ("long" in rest) {
// return {
@ -87,13 +89,13 @@ export function compileVP(
export function compileVP(
VP: T.VPRendered,
form: T.FormVersion,
combineLengths: true,
combineLengths: boolean,
blankOut?: BlankoutOptions
): { ps: T.PsString[]; e?: string[] };
export function compileVP(
VP: T.VPRendered,
form: T.FormVersion,
combineLengths?: true,
combineLengths?: boolean,
blankOut?: BlankoutOptions
): { ps: T.SingleOrLengthOpts<T.PsString[]>; e?: string[] } {
// const verb = getVerbFromBlocks(VP.blocks).block;

View File

@ -868,6 +868,14 @@ export type PossesorSelection = {
np: NPSelection;
};
export type Noun = {
entry: NounEntry;
gender: Gender;
number: "sing" | "plur";
adjectives: AdjectiveSelection[];
possesor: undefined | Noun;
};
// TODO require/import Person and PsString
export type NounSelection = {
type: "noun";