diff --git a/src/demo-components/ParserDemo.tsx b/src/demo-components/ParserDemo.tsx index 86af1d6..517ec3d 100644 --- a/src/demo-components/ParserDemo.tsx +++ b/src/demo-components/ParserDemo.tsx @@ -7,16 +7,19 @@ import { tokenizer } from "../lib/src/parsing/tokenizer"; function ParserDemo({ opts }: { opts: T.TextOptions }) { const [text, setText] = useState(""); const [result, setResult] = useState(""); + const [errors, setErrors] = useState([]); function handleChange(e: React.ChangeEvent) { const value = e.target.value; if (!value) { setText(""); setResult(""); + setErrors([]); return; } - const r = parsePhrase(tokenizer(value), lookup); + const { success, errors } = parsePhrase(tokenizer(value), lookup); setText(value); - setResult(JSON.stringify(r, null, " ")); + setErrors(errors); + setResult(JSON.stringify(success, null, " ")); } return (
@@ -32,6 +35,13 @@ function ParserDemo({ opts }: { opts: T.TextOptions }) { onChange={handleChange} />
+ {result === "[]" && errors.length > 0 && ( +
+ {errors.map((e) => ( +
{e}
+ ))} +
+ )}
{result}
diff --git a/src/lib/src/parsing/inflection-query.ts b/src/lib/src/parsing/inflection-query.ts index 9a29ca7..5353c5b 100644 --- a/src/lib/src/parsing/inflection-query.ts +++ b/src/lib/src/parsing/inflection-query.ts @@ -6,6 +6,7 @@ import { isPattern, isPattern5Entry, isPattern4Entry, + isPattern6FemEntry, } from "../type-predicates"; import { equals } from "rambda"; @@ -135,6 +136,14 @@ export function getInflectionQueries( predicate: (e) => isPattern2Entry(e) || isPattern3Entry(e), }, }); + queries.push({ + search: { p: s }, + details: { + inflection: [0], + gender: ["fem"], + predicate: isPattern6FemEntry, + }, + }); } else if (s.endsWith("و")) { queries.push({ search: { p: s.slice(0, -1) }, @@ -179,6 +188,24 @@ export function getInflectionQueries( predicate: isPattern3Entry, }, }); + if (includeNouns) { + queries.push({ + search: { p: s.slice(0, -1) + "ي" }, + details: { + inflection: [1], + gender: ["fem"], + predicate: isPattern6FemEntry, + }, + }); + queries.push({ + search: { p: s }, + details: { + inflection: [0, 1], + gender: ["fem"], + predicate: isPattern3Entry, + }, + }); + } } const coallated: ReturnType = []; diff --git a/src/lib/src/parsing/parse-adjective.test.ts b/src/lib/src/parsing/parse-adjective.test.ts index c9b7c92..71e5cae 100644 --- a/src/lib/src/parsing/parse-adjective.test.ts +++ b/src/lib/src/parsing/parse-adjective.test.ts @@ -313,7 +313,12 @@ describe("parsing adjectives", () => { cases.forEach(({ input, output }) => { const tokens = tokenizer(input); const possibilities = parseAdjective(tokens, lookup).map((x) => x[1]); - expect(possibilities).toEqual(output); + expect( + possibilities.map((x) => { + const { given, ...rest } = x; + return rest; + }) + ).toEqual(output); }); }); }); diff --git a/src/lib/src/parsing/parse-adjective.ts b/src/lib/src/parsing/parse-adjective.ts index d057111..2ff681b 100644 --- a/src/lib/src/parsing/parse-adjective.ts +++ b/src/lib/src/parsing/parse-adjective.ts @@ -11,6 +11,7 @@ export function parseAdjective( { inflection: (0 | 1 | 2)[]; gender: T.Gender[]; + given: string; selection: T.AdjectiveSelection; } ][] { @@ -32,6 +33,7 @@ export function parseAdjective( selection, inflection: deets.inflection, gender: deets.gender, + given: first, }, ]); }); diff --git a/src/lib/src/parsing/parse-noun.ts b/src/lib/src/parsing/parse-noun.ts index c9ab49d..47c1479 100644 --- a/src/lib/src/parsing/parse-noun.ts +++ b/src/lib/src/parsing/parse-noun.ts @@ -15,17 +15,28 @@ export function parseNoun( adjectives: { inflection: (0 | 1 | 2)[]; gender: T.Gender[]; + given: string; selection: T.AdjectiveSelection; }[] -): [string[], { inflection: (0 | 1 | 2)[]; selection: T.NounSelection }][] { +): { + success: [ + string[], + { inflection: (0 | 1 | 2)[]; selection: T.NounSelection } + ][]; + errors: string[]; +} { if (tokens.length === 0) { - return []; + return { + success: [], + errors: [], + }; } const adjRes = parseAdjective(tokens, lookup); - const withAdj = adjRes.flatMap(([tkns, adj]) => + const withAdj = adjRes.map(([tkns, adj]) => parseNoun(tkns, lookup, [...adjectives, adj]) ); - const w: ReturnType = []; + const success: ReturnType["success"] = []; + const errors: string[] = []; const [first, ...rest] = tokens; const searches = getInflectionQueries(first, true); @@ -34,11 +45,15 @@ export function parseNoun( details.forEach((deets) => { const fittingEntries = nounEntries.filter(deets.predicate); fittingEntries.forEach((entry) => { - console.log({ entry, deets }); if (isUnisexNounEntry(entry)) { deets.gender.forEach((gender) => { - if (adjsMatch(adjectives, gender, deets.inflection)) { - w.push([ + const { ok, error } = adjsMatch( + adjectives, + gender, + deets.inflection + ); + if (ok) { + success.push([ rest, { inflection: deets.inflection, @@ -49,11 +64,16 @@ export function parseNoun( }, }, ]); + } else { + error.forEach((e) => { + errors.push(e); + }); } }); } else if (isMascNounEntry(entry) && deets.gender.includes("masc")) { - if (adjsMatch(adjectives, "masc", deets.inflection)) { - w.push([ + const { ok, error } = adjsMatch(adjectives, "masc", deets.inflection); + if (ok) { + success.push([ rest, { inflection: deets.inflection, @@ -63,10 +83,15 @@ export function parseNoun( }, }, ]); + } else { + error.forEach((e) => { + errors.push(e); + }); } } else if (isFemNounEntry(entry) && deets.gender.includes("fem")) { - if (adjsMatch(adjectives, "fem", deets.inflection)) { - w.push([ + const { ok, error } = adjsMatch(adjectives, "fem", deets.inflection); + if (ok) { + success.push([ rest, { inflection: deets.inflection, @@ -76,22 +101,63 @@ export function parseNoun( }, }, ]); + } else { + error.forEach((e) => { + errors.push(e); + }); } } }); }); }); - return [...withAdj, ...w]; + return { + success: [...withAdj.map((x) => x.success).flat(), ...success], + errors: [...withAdj.map((x) => x.errors).flat(), ...errors], + }; } function adjsMatch( adjectives: Parameters[2], gender: T.Gender, inflection: (0 | 1 | 2)[] -): boolean { - return adjectives.every( +): { ok: boolean; error: string[] } { + const unmatching = adjectives.filter( (adj) => - adj.gender.includes(gender) && - adj.inflection.some((i) => inflection.includes(i)) + !adj.gender.includes(gender) || + !adj.inflection.some((i) => inflection.includes(i)) ); + if (unmatching.length) { + return { + ok: false, + error: unmatching.map((x) => { + const adjText = + x.given === x.selection.entry.p + ? x.given + : `${x.given} (${x.selection.entry.p})`; + const inflectionIssue = !x.inflection.some((x) => + inflection.includes(x) + ) + ? ` should be ${showInflection(inflection)}` + : ``; + return `Adjective agreement error: ${adjText} should be ${inflectionIssue} ${gender}.`; + }), + }; + } else { + return { + ok: true, + error: [], + }; + } +} + +function showInflection(inf: (0 | 1 | 2)[]): string { + const [last, ...rest] = inf.reverse(); + const template = rest.length + ? `${rest.join(", ")}, or ${last}` + : last.toString(); + console.log(template); + return template + .replace("0", "plain") + .replace("1", "first inflection") + .replace("2", "second inflection"); } diff --git a/src/lib/src/parsing/parse-phrase.ts b/src/lib/src/parsing/parse-phrase.ts index d6021d9..f72711a 100644 --- a/src/lib/src/parsing/parse-phrase.ts +++ b/src/lib/src/parsing/parse-phrase.ts @@ -6,13 +6,19 @@ import { parseNoun } from "./parse-noun"; export function parsePhrase( s: string[], lookup: (s: Partial) => T.DictionaryEntry[] -): any[] { +): { + success: any[]; + errors: string[]; +} { const adjsRes = parseAdjective(s, lookup); const prnsRes = parsePronoun(s); const nounsRes = parseNoun(s, lookup, []); - const correct = [...adjsRes, ...prnsRes, ...nounsRes] + const correct = [...adjsRes, ...prnsRes, ...nounsRes.success] .filter(([tkns]) => tkns.length === 0) .map((x) => x[1]); - return correct; + return { + success: correct, + errors: nounsRes.errors, + }; }