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", "name": "pashto-inflector",
"version": "7.0.9", "version": "7.0.11",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pashto-inflector", "name": "pashto-inflector",
"version": "7.0.9", "version": "7.0.11",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@lingdocs/ps-react", "name": "@lingdocs/ps-react",
"version": "7.0.9", "version": "7.0.11",
"description": "Pashto inflector library module with React components", "description": "Pashto inflector library module with React components",
"main": "dist/components/library.js", "main": "dist/components/library.js",
"module": "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"; import VPExplorerQuiz from "./VPExplorerQuiz";
// @ts-ignore // @ts-ignore
import LZString from "lz-string"; 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 { getObjectSelection } from "../../../lib/src/phrase-building/blocks-utils";
import VPPicker from "./VPPicker"; import VPPicker from "./VPPicker";
import AllTensesDisplay from "./AllTensesDisplay"; import AllTensesDisplay from "./AllTensesDisplay";
@ -34,6 +37,7 @@ function VPExplorer(props: {
handleLinkClick: ((ts: number) => void) | "none"; handleLinkClick: ((ts: number) => void) | "none";
entryFeeder: T.EntryFeeder; entryFeeder: T.EntryFeeder;
onlyPhrases?: boolean; onlyPhrases?: boolean;
eventEmitter?: (e: string) => void;
}) { }) {
const [vps, adjustVps] = useStickyReducer( const [vps, adjustVps] = useStickyReducer(
vpsReducer, vpsReducer,
@ -85,7 +89,7 @@ function VPExplorer(props: {
const VPSFromUrl = getVPSFromUrl(); const VPSFromUrl = getVPSFromUrl();
if (VPSFromUrl) { if (VPSFromUrl) {
setMode("phrases"); setMode("phrases");
adjustVps({ eventWrapper(adjustVps)({
type: "load vps", type: "load vps",
payload: VPSFromUrl, payload: VPSFromUrl,
}); });
@ -93,7 +97,15 @@ function VPExplorer(props: {
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
function handleSubjObjSwap() { 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) { function quizLock<T>(f: T) {
if (mode === "quiz") { if (mode === "quiz") {
@ -105,7 +117,7 @@ function VPExplorer(props: {
return f; return f;
} }
function handleSetForm(form: T.FormVersion) { function handleSetForm(form: T.FormVersion) {
adjustVps({ eventWrapper(adjustVps)({
type: "set form", type: "set form",
payload: form, payload: form,
}); });
@ -134,7 +146,7 @@ function VPExplorer(props: {
<div className="mt-3" style={{ maxWidth: "950px" }}> <div className="mt-3" style={{ maxWidth: "950px" }}>
<VerbPicker <VerbPicker
vps={vps} vps={vps}
onChange={quizLock(adjustVps)} onChange={quizLock(eventWrapper(adjustVps))}
opts={props.opts} opts={props.opts}
handleLinkClick={props.handleLinkClick} handleLinkClick={props.handleLinkClick}
/> />
@ -190,13 +202,19 @@ function VPExplorer(props: {
<VPPicker <VPPicker
opts={props.opts} opts={props.opts}
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder}
onChange={(payload) => adjustVps({ type: "load vps", payload })} onChange={(payload) =>
eventWrapper(adjustVps)({ type: "load vps", payload })
}
vps={vps} vps={vps}
/> />
)} )}
{mode !== "phrases" && ( {mode !== "phrases" && (
<div className="my-2"> <div className="my-2">
<TensePicker vps={vps} onChange={adjustVps} mode={mode} /> <TensePicker
vps={vps}
onChange={eventWrapper(adjustVps)}
mode={mode}
/>
</div> </div>
)} )}
{mode === "phrases" && ( {mode === "phrases" && (
@ -207,7 +225,7 @@ function VPExplorer(props: {
object={object} object={object}
VS={vps.verb} VS={vps.verb}
opts={props.opts} opts={props.opts}
onChange={adjustVps} onChange={eventWrapper(adjustVps)}
/> />
)} )}
{mode === "quiz" && <VPExplorerQuiz opts={props.opts} vps={vps} />} {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 { getVerbInfo } from "../lib/src/verb-info";
import verbs from "../verbs"; import verbs from "../verbs";
import { useStickyState } from "../components/library"; import { useStickyState } from "../components/library";
import { import { clamp } from "../lib/src/p-text-helpers";
clamp import { randomNumber } from "../lib/src/misc-helpers";
} from "../lib/src/p-text-helpers";
import {
randomNumber,
} from "../lib/src/misc-helpers";
import { entryFeeder } from "./entryFeeder"; import { entryFeeder } from "./entryFeeder";
const transitivities: T.Transitivity[] = [ const transitivities: T.Transitivity[] = [
"transitive", "transitive",
"intransitive", "intransitive",
"grammatically transitive", "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, verb: v,
info: getVerbInfo(v.entry, v.complement), info: getVerbInfo(v.entry, v.complement),
})); })
);
type VerbType = "simple" | "stative compound" | "dynamic compound"; type VerbType = "simple" | "stative compound" | "dynamic compound";
const verbTypes: VerbType[] = [ const verbTypes: VerbType[] = [
"simple", "simple",
"stative compound", "stative compound",
"dynamic compound", "dynamic compound",
]; ];
function VPBuilderDemo({ opts }: { function VPBuilderDemo({ opts }: { opts: T.TextOptions }) {
opts: T.TextOptions, const [verbTs, setVerbTs] = useStickyState<number>(0, "verbTs1");
}) { const [verbTypeShowing, setVerbTypeShowing] = useStickyState<VerbType>(
const [verbTs, setVerbTs] = useStickyState<number>(0, "verbTs1"); "simple",
const [verbTypeShowing, setVerbTypeShowing] = useStickyState<VerbType>("simple", "vTypeShowing"); "vTypeShowing"
const [transitivityShowing, setTransitivityShowing] = useStickyState<T.Transitivity>("intransitive", "transitivityShowing1"); );
// const onlyGrammTrans = (arr: Transitivity[]) => ( const [transitivityShowing, setTransitivityShowing] =
// arr.length === 1 && arr[0] === "grammatically transitive" useStickyState<T.Transitivity>("intransitive", "transitivityShowing1");
// ); // const onlyGrammTrans = (arr: Transitivity[]) => (
// const ensureSimpleVerbTypeSelected = () => { // arr.length === 1 && arr[0] === "grammatically transitive"
// if (!verbTypesShowing.includes["simple"]) { // );
// setVerbTypesShowing([...verbTypesShowing, "simple"]); // const ensureSimpleVerbTypeSelected = () => {
// } // if (!verbTypesShowing.includes["simple"]) {
// } // setVerbTypesShowing([...verbTypesShowing, "simple"]);
const handleVerbIndexChange = (e: any) => { // }
setVerbTs(parseInt(e.target.value)); // }
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) => { if (
const type = e.target.value as VerbType; type === "stative compound" &&
if (type === "dynamic compound") { transitivityShowing === "grammatically transitive"
setTransitivityShowing("transitive"); ) {
} setTransitivityShowing("transitive");
if (type === "stative compound" && transitivityShowing === "grammatically transitive") {
setTransitivityShowing("transitive");
}
setVerbTypeShowing(type);
} }
const handleTransitivitySelection = (e: any) => { setVerbTypeShowing(type);
const transitivity = e.target.value as T.Transitivity; };
if (transitivity === "grammatically transitive") { const handleTransitivitySelection = (e: any) => {
setVerbTypeShowing("simple"); const transitivity = e.target.value as T.Transitivity;
} if (transitivity === "grammatically transitive") {
if (transitivity === "intransitive" && verbTypeShowing === "dynamic compound") { setVerbTypeShowing("simple");
setTransitivityShowing("transitive");
return;
}
setTransitivityShowing(e.target.value as T.Transitivity);
} }
const verbsAvailable = allVerbs.filter((verb) => ( if (
( transitivity === "intransitive" &&
(verb.info.type === "transitive or grammatically transitive simple" && verbTypeShowing === "simple") && (transitivityShowing === "transitive" || transitivityShowing === "grammatically transitive") verbTypeShowing === "dynamic compound"
) || ) {
(( setTransitivityShowing("transitive");
verbTypeShowing === verb.info.type || return;
(verbTypeShowing === "stative compound" && verb.info.type === "dynamic or stative compound") || }
(verbTypeShowing === "dynamic compound" && verb.info.type === "dynamic or stative compound") || setTransitivityShowing(e.target.value as T.Transitivity);
(verbTypeShowing === "dynamic compound" && verb.info.type === "dynamic or generative stative compound") || };
(verbTypeShowing === "stative compound" && verb.info.type === "dynamic or generative stative compound") const verbsAvailable = allVerbs
) .filter(
&& ( (verb) =>
transitivityShowing === verb.info.transitivity (verb.info.type === "transitive or grammatically transitive simple" &&
)) verbTypeShowing === "simple" &&
)).sort((a, b) => a.verb.entry.p.localeCompare(b.verb.entry.p, "ps")); (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 v = (() => {
const vFound = verbsAvailable.find(v => v.verb.entry.ts === verbTs); const vFound = verbsAvailable.find((v) => v.verb.entry.ts === verbTs);
if (vFound) return vFound; if (vFound) return vFound;
if (verbsAvailable.length === 0) return undefined; if (verbsAvailable.length === 0) return undefined;
const vTopOfList = verbsAvailable[0]; const vTopOfList = verbsAvailable[0];
setVerbTs(vTopOfList.verb.entry.ts); setVerbTs(vTopOfList.verb.entry.ts);
return vTopOfList; return vTopOfList;
})(); })();
const pickRandomVerb = () => { const pickRandomVerb = () => {
let newIndex: number; let newIndex: number;
do { do {
newIndex = randomNumber(0, verbsAvailable.length); newIndex = randomNumber(0, verbsAvailable.length);
} while(verbsAvailable[newIndex].verb.entry.ts === verbTs); } while (verbsAvailable[newIndex].verb.entry.ts === verbTs);
setVerbTs(verbsAvailable[newIndex].verb.entry.ts); setVerbTs(verbsAvailable[newIndex].verb.entry.ts);
}; };
const makeVerbLabel = (entry: T.DictionaryEntry): string => ( const makeVerbLabel = (entry: T.DictionaryEntry): string =>
`${entry.p} - ${clamp(entry.e, 20)}` `${entry.p} - ${clamp(entry.e, 20)}`;
); // const rs = v ? getAllRs(v.verb as T.VerbEntry) : undefined
// const rs = v ? getAllRs(v.verb as T.VerbEntry) : undefined return (
return <div className="mt-4"> <div className="mt-4">
<div className="d-block mx-auto card" style={{ maxWidth: "700px", background: "var(--closer)"}}> <div
<div className="card-body"> className="d-block mx-auto card"
<div className="row"> style={{ maxWidth: "700px", background: "var(--closer)" }}
<div className="col-sm-6"> >
{v ? <div className="card-body">
<div> <div className="row">
<div className="mb-1">Select a verb:</div> <div className="col-sm-6">
<div className="input-group"> {v ? (
<select className="custom-select" value={verbTs} onChange={handleVerbIndexChange}> <div>
{verbsAvailable.length <div className="mb-1">Select a verb:</div>
? verbsAvailable.map((v, i) => ( <div className="input-group">
<option value={v.verb.entry.ts} key={i} dir="ltr"> <select
{"\u200E"}{makeVerbLabel(v.verb.entry)} className="custom-select"
</option> value={verbTs}
)) onChange={handleVerbIndexChange}
: <option>Select a verb type</option> >
} {verbsAvailable.length ? (
</select> verbsAvailable.map((v, i) => (
<div className="input-group-append"> <option value={v.verb.entry.ts} key={i} dir="ltr">
<button className="btn btn-secondary" onClick={pickRandomVerb}> {"\u200E"}
<i className="fas fa-random" /> {makeVerbLabel(v.verb.entry)}
</button> </option>
</div> ))
</div> ) : (
<div className="my-3"> <option>Select a verb type</option>
<div> )}
<strong> </select>
<Pashto opts={opts}>{v.verb.entry}</Pashto> <div className="input-group-append">
{` `}-{` `} <button
<Phonetics opts={opts}>{v.verb.entry}</Phonetics> className="btn btn-secondary"
</strong> onClick={pickRandomVerb}
{` `} >
<em>{v.verb.entry.c}</em> <i className="fas fa-random" />
</div> </button>
<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>
<div className="col-sm-6"> </div>
<h6>Verb type:</h6> <div className="my-3">
<div onChange={handleTypeSelection}> <div>
{verbTypes.map((type) => ( <strong>
<div key={type} className="form-check"> <Pashto opts={opts}>{v.verb.entry}</Pashto>
<input {` `}-{` `}
className="form-check-input" <Phonetics opts={opts}>{v.verb.entry}</Phonetics>
type="radio" </strong>
name="verb-type" {` `}
checked={verbTypeShowing === type} <em>{v.verb.entry.c}</em>
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 className="ml-3">{v.verb.entry.e}</div>
</div>
</div> </div>
) : (
<div className="alert alert-warning mb-4" role="alert">
No such verbs available...
</div>
)}
</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> </div>
{v?.verb.entry && <div style={{ paddingBottom: "20px" }}> </div>
<PhraseBuilder {v?.verb.entry && (
handleLinkClick="none" <div style={{ paddingBottom: "20px" }}>
verb={v.verb as T.VerbEntry} <PhraseBuilder
entryFeeder={entryFeeder} handleLinkClick="none"
opts={opts} verb={v.verb as T.VerbEntry}
/> entryFeeder={entryFeeder}
</div>} opts={opts}
</div>; />
</div>
)}
</div>
);
} }
export default VPBuilderDemo; export default VPBuilderDemo;

View File

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

View File

@ -4,9 +4,9 @@ import { parseEquative } from "./parse-equative";
import { parseKidsSection } from "./parse-kids-section"; import { parseKidsSection } from "./parse-kids-section";
import { parseNeg } from "./parse-negative"; import { parseNeg } from "./parse-negative";
import { parseNPAP } from "./parse-npap"; import { parseNPAP } from "./parse-npap";
import { parsePastPart } from "./parse-past-part"; import { parseVBP } from "./parse-vbp";
import { parsePH } from "./parse-ph"; import { parsePH } from "./parse-ph";
import { parseVerb } from "./parse-verb"; import { parseVBE } from "./parse-vbe";
import { import {
bindParseResult, bindParseResult,
returnParseResult, returnParseResult,
@ -14,6 +14,7 @@ import {
isParsedVBP, isParsedVBP,
startsVerbSection, startsVerbSection,
} from "./utils"; } from "./utils";
import { isKedulStatEntry } from "./parse-verb-helpers";
export function parseBlocks( export function parseBlocks(
tokens: Readonly<T.Token[]>, tokens: Readonly<T.Token[]>,
@ -32,14 +33,15 @@ export function parseBlocks(
(b): b is T.ParsedPH => b.type === "PH" (b): b is T.ParsedPH => b.type === "PH"
); );
// TOOD: rather parse VBP / VBE
const allBlocks: T.ParseResult<T.ParsedBlock | T.ParsedKidsSection>[] = [ const allBlocks: T.ParseResult<T.ParsedBlock | T.ParsedKidsSection>[] = [
...(!inVerbSection ? parseNPAP(tokens, lookup) : []), ...(!inVerbSection ? parseNPAP(tokens, lookup) : []),
// ensure at most one of each PH, VBE, VBP // ensure at most one of each PH, VBE, VBP
...(prevPh ? [] : parsePH(tokens)), ...(prevPh ? [] : parsePH(tokens)),
...(blocks.some(isParsedVBE) ...(blocks.some(isParsedVBE)
? [] ? []
: [...parseVerb(tokens, lookup), ...parseEquative(tokens)]), : [...parseVBE(tokens, lookup), ...parseEquative(tokens)]),
...(blocks.some(isParsedVBP) ? [] : parsePastPart(tokens, lookup)), ...(blocks.some(isParsedVBP) ? [] : parseVBP(tokens, lookup)),
...(blocks.some((b) => b.type === "negative") ? [] : parseNeg(tokens)), ...(blocks.some((b) => b.type === "negative") ? [] : parseNeg(tokens)),
...parseKidsSection(tokens, []), ...parseKidsSection(tokens, []),
]; ];
@ -82,12 +84,24 @@ function phMatches(
if (!ph) { if (!ph) {
return true; return true;
} }
if (!vb) { if (!vb) {
return true; return true;
} }
if (vb.info.type !== "verb") { if (vb.info.type !== "verb") {
return false; 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); const verbPh = getPhFromVerb(vb.info.verb, vb.info.base);
return verbPh === ph.s; 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, raatlul,
} from "./irreg-verbs"; } from "./irreg-verbs";
import { lookup, wordQuery } from "./lookup"; import { lookup, wordQuery } from "./lookup";
import { parseVerb } from "./parse-verb"; import { parseVBE } from "./parse-vbe";
import { tokenizer } from "./tokenizer"; import { tokenizer } from "./tokenizer";
import { getPeople, removeKeys } from "./utils"; 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: "شي", input: "شي",
output: [ output: [
@ -965,42 +962,6 @@ const tests: {
}, },
verb: kedulStat, 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, () => { test(label, () => {
cases.forEach(({ input, output }) => { cases.forEach(({ input, output }) => {
const tokens = tokenizer(input); 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) => { const madeVbsS = output.reduce<T.ParsedVBE[]>((acc, o) => {
return [ return [
...acc, ...acc,

View File

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

View File

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

View File

@ -2,13 +2,23 @@ import * as T from "../../../types";
import { LookupFunction } from "./lookup"; import { LookupFunction } from "./lookup";
import { returnParseResult } from "./utils"; import { returnParseResult } from "./utils";
export function parsePastPart( export function parseVBP(
tokens: Readonly<T.Token[]>, tokens: Readonly<T.Token[]>,
lookup: LookupFunction lookup: LookupFunction
): T.ParseResult<T.ParsedVBP>[] { ): T.ParseResult<T.ParsedVBP>[] {
if (tokens.length === 0) { if (tokens.length === 0) {
return []; return [];
} }
return [
...parsePastPart(tokens, lookup),
// ...parseAbility(tokens),
];
}
function parsePastPart(
tokens: Readonly<T.Token[]>,
lookup: LookupFunction
): T.ParseResult<T.ParsedVBP>[] {
const [{ s }, ...rest] = tokens; const [{ s }, ...rest] = tokens;
const ending: "ی" | "ي" | "ې" = s.at(-1) as "ی" | "ي" | "ې"; const ending: "ی" | "ي" | "ې" = s.at(-1) as "ی" | "ي" | "ې";
if (!ending || !["ی", "ي", "ې"].includes(ending)) { if (!ending || !["ی", "ي", "ې"].includes(ending)) {
@ -32,6 +42,34 @@ export function parsePastPart(
.flatMap((m) => returnParseResult(rest, m)); .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[] { function endingGenderNum(ending: "ی" | "ي" | "ې"): T.GenderNumber[] {
if (ending === "ی") { if (ending === "ی") {
return [ 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.type === "verb") {
if (verb.info.aspect === "perfective") { if (verb.info.aspect === "perfective") {
// TODO: handle kedul sh perfective
if (!ph) { if (!ph) {
return []; return [];
} }

View File

@ -62,6 +62,18 @@ export function bindParseResult<C, D>(
return cleanOutResults(nextPossibilities); 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>( export function returnParseResultS<D>(
tokens: Readonly<T.Token[]>, tokens: Readonly<T.Token[]>,
body: D, body: D,

View File

@ -34,6 +34,8 @@ type BlankoutOptions = {
predicate?: boolean; 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[]> { // function compilePs({ blocks, kids, verb: { head, rest }, VP }: CompilePsInput): T.SingleOrLengthOpts<T.PsString[]> {
// if ("long" in rest) { // if ("long" in rest) {
// return { // return {
@ -87,13 +89,13 @@ export function compileVP(
export function compileVP( export function compileVP(
VP: T.VPRendered, VP: T.VPRendered,
form: T.FormVersion, form: T.FormVersion,
combineLengths: true, combineLengths: boolean,
blankOut?: BlankoutOptions blankOut?: BlankoutOptions
): { ps: T.PsString[]; e?: string[] }; ): { ps: T.PsString[]; e?: string[] };
export function compileVP( export function compileVP(
VP: T.VPRendered, VP: T.VPRendered,
form: T.FormVersion, form: T.FormVersion,
combineLengths?: true, combineLengths?: boolean,
blankOut?: BlankoutOptions blankOut?: BlankoutOptions
): { ps: T.SingleOrLengthOpts<T.PsString[]>; e?: string[] } { ): { ps: T.SingleOrLengthOpts<T.PsString[]>; e?: string[] } {
// const verb = getVerbFromBlocks(VP.blocks).block; // const verb = getVerbFromBlocks(VP.blocks).block;

View File

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