Skip to content

Commit 230e69f

Browse files
committed
implement checked verb parser
1 parent 69c3023 commit 230e69f

File tree

2 files changed

+175
-179
lines changed

2 files changed

+175
-179
lines changed

dictionary/parser.ts

Lines changed: 163 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ import {
1313
checkedSequence,
1414
choiceOnlyOne,
1515
choiceWithCheck,
16-
lookAhead,
1716
match,
1817
matchString,
1918
notEnd,
19+
nothing,
2020
optionalAll,
2121
optionalWithCheck,
2222
Parser,
2323
sequence,
2424
UnexpectedError,
2525
withSource,
2626
} from "../src/parser/parser_lib.ts";
27-
import { Definition, Dictionary, VerbForms } from "./type.ts";
27+
import { Definition, Dictionary, Noun, PartialVerb } from "./type.ts";
2828

2929
const RESERVED_SYMBOLS = "#()*+/:;<=>@[\\]^`{|}~";
3030

@@ -249,88 +249,31 @@ const noun = sequence(
249249
postAdjective,
250250
}) as const
251251
);
252-
function verbOnly(tagInside: Parser<unknown>): Parser<VerbForms> {
253-
return choiceWithCheck(
254-
checkedSequence(
255-
word.skip(slash),
256-
sequence(
257-
word.skip(slash),
258-
word.skip(tag(tagInside)),
259-
),
260-
)
261-
.map(([presentPlural, [presentSingular, past]]) => ({
262-
presentPlural,
263-
presentSingular,
264-
past,
265-
}))
266-
.filter(({ presentPlural, presentSingular, past }) => {
267-
const [_, ...pluralParticles] = presentPlural.split(" ");
268-
const [_1, ...singularParticles] = presentSingular.split(" ");
269-
const [_2, ...pastParticles] = past.split(" ");
270-
const allMatched =
271-
pluralParticles.length === singularParticles.length &&
272-
pluralParticles.length === pastParticles.length &&
273-
pluralParticles.every((particle, i) =>
274-
particle === singularParticles[i] && particle === pastParticles[i]
275-
);
276-
if (allMatched) {
277-
return true;
278-
} else {
279-
throw new ArrayResultError(
280-
"mismatched verb particles " +
281-
`"${presentPlural}/${presentSingular}/${past}"`,
282-
);
283-
}
284-
}),
285-
checkedAsWhole(unescapedWord.skip(tag(tagInside)))
286-
.map((verb) => {
287-
const sentence = nlp(verb);
288-
sentence.tag("Verb");
289-
const conjugations = sentence.verbs().conjugate()[0] as undefined | {
290-
Infinitive: string;
291-
PastTense: string;
292-
PresentTense: string;
293-
Gerund: string;
294-
FutureTense: string;
295-
};
296-
if (conjugations == null) {
297-
throw new ArrayResultError(
298-
`no verb conjugation found for "${verb}". consider providing ` +
299-
"all conjugations instead",
300-
);
301-
}
302-
if (verb !== conjugations.Infinitive) {
303-
throw new ArrayResultError(
304-
`conjugation error: "${verb}" is not ` +
305-
`"${conjugations.Infinitive}". consider providing all ` +
306-
"conjugations instead",
307-
);
308-
}
309-
return {
310-
presentPlural: escapeHtml(conjugations.Infinitive),
311-
presentSingular: escapeHtml(conjugations.PresentTense),
312-
past: escapeHtml(conjugations.PastTense),
313-
};
314-
}),
252+
const checkedNoun = new CheckedParser(
253+
choiceOnlyOne(
254+
determiner.check,
255+
adjective.check,
256+
nounOnly.check,
257+
),
258+
noun,
259+
);
260+
function simpleDefinitionWith<T>(
261+
tag: Parser<unknown>,
262+
after: Parser<T>,
263+
): CheckedParser<readonly [string, T]> {
264+
return checkedSequence(
265+
word.skip(openParenthesis).skip(tag),
266+
closeParenthesis.with(after),
315267
);
316268
}
317-
const verb = verbOnly(keyword("v"));
318-
const linkingVerb = verbOnly(sequence(keyword("v"), keyword("linking")));
319269
function simpleDefinition(tag: Parser<unknown>): CheckedParser<string> {
320-
return checkedSequence(
321-
word.skip(openParenthesis).skip(tag),
322-
closeParenthesis,
323-
)
324-
.map(([word]) => word);
270+
return simpleDefinitionWith(tag, nothing).map(([word]) => word);
325271
}
326272
function simpleDefinitionWithTemplate(
327273
tag: Parser<unknown>,
328274
templateInside: Parser<unknown>,
329275
): CheckedParser<string> {
330-
return checkedSequence(
331-
word.skip(openParenthesis).skip(tag),
332-
closeParenthesis.skip(template(templateInside)),
333-
)
276+
return simpleDefinitionWith(tag, template(templateInside))
334277
.map(([word]) => word);
335278
}
336279
const interjectionDefinition = simpleDefinition(keyword("i"))
@@ -463,99 +406,153 @@ const compoundAdjectiveDefinition = checkedSequence(
463406
adjective.every((adjective) => adjective.adverb.length === 0) ||
464407
throwError(new ArrayResultError("compound adjective cannot have adverb"))
465408
);
466-
// const verbDefinition_ = checkedSequence(
467-
// sequence(
468-
// unescapedWord,
469-
// optionalWithCheck(
470-
// checkedSequence(slash, sequence(word.skip(slash), word))
471-
// .map(([_, forms]) => forms),
472-
// )
473-
// .parser
474-
// .skip(sequence(openParenthesis, keyword("v"))),
475-
// ),
476-
// );
477-
478-
// (v modal)
479-
// (v linking) [predicate]
480-
// (v) [predicate]
481-
// (v) [object] Noun?
482-
// (v) Noun? (prep)? [object]?
483-
484-
const verbDefinition = choiceOnlyOne<Definition>(
409+
const verbDefinition = checkedSequence(
485410
sequence(
486-
verb,
487-
optionalAll(template(keyword("object"))),
411+
unescapedWord,
488412
optionalWithCheck(
489-
checkedSequence(simpleUnit("prep"), noun)
490-
.map(([preposition, object]) => ({ preposition, object })),
413+
checkedSequence(slash, sequence(word.skip(slash), word))
414+
.map(([_, forms]) => forms),
491415
)
492-
.map(nullableAsArray),
493-
)
494-
.skip(lookAhead(semicolon))
495-
.map(([verb, forObject, indirectObject]) => ({
496-
...verb,
497-
type: "verb",
498-
directObject: null,
499-
indirectObject,
500-
forObject: forObject != null,
501-
predicateType: null,
502-
})),
503-
sequence(
504-
verb,
505-
optionalWithCheck(
506-
new CheckedParser(
507-
choiceOnlyOne(determiner.check, adjective.check, nounOnly.check),
508-
noun,
416+
.skip(sequence(openParenthesis, keyword("v"))),
417+
),
418+
choiceWithCheck<null | PartialVerb>(
419+
checkedSequence(
420+
keyword("modal"),
421+
sequence(closeParenthesis, template(keyword("predicate"))),
422+
)
423+
.map(() => null),
424+
checkedSequence(
425+
keyword("linking"),
426+
sequence(closeParenthesis, template(keyword("predicate"))),
427+
)
428+
.map(() => ({
429+
directObject: null,
430+
indirectObject: [],
431+
forObject: false,
432+
predicateType: "noun adjective",
433+
})),
434+
checkedSequence(
435+
sequence(closeParenthesis, openBracket, keyword("predicate")),
436+
closeBracket,
437+
)
438+
.map(() => ({
439+
directObject: null,
440+
indirectObject: [],
441+
forObject: false,
442+
predicateType: "verb",
443+
})),
444+
checkedSequence(
445+
sequence(closeParenthesis, openBracket, keyword("object")),
446+
closeBracket
447+
.with(optionalWithCheck(
448+
simpleDefinitionWith(keyword("prep"), noun)
449+
.map(([preposition, object]) => ({ preposition, object }) as const),
450+
))
451+
.map(nullableAsArray),
452+
)
453+
.map(([_, indirectObject]) => ({
454+
directObject: null,
455+
indirectObject,
456+
forObject: true,
457+
predicateType: null,
458+
})),
459+
new CheckedParser(
460+
nothing,
461+
sequence(
462+
closeParenthesis
463+
.with(
464+
optionalWithCheck(checkedNoun),
465+
),
466+
optionalWithCheck(
467+
simpleDefinitionWith(
468+
keyword("prep"),
469+
choiceWithCheck<"template" | Noun>(
470+
checkedSequence(
471+
openBracket,
472+
sequence(keyword("object"), closeBracket),
473+
)
474+
.map(() => "template" as const),
475+
checkedNoun,
476+
),
477+
),
478+
),
509479
),
510-
),
511-
optionalWithCheck(
512-
checkedSequence(
513-
simpleUnit("prep"),
514-
template(keyword("object")),
515-
)
516-
.map(([preposition]) => preposition),
517-
),
518-
)
519-
.skip(lookAhead(semicolon))
520-
.map(([verb, directObject, preposition]) => ({
521-
...verb,
522-
type: "verb",
523-
directObject,
524-
indirectObject: [],
525-
forObject: preposition ?? false,
526-
predicateType: null,
527-
})),
528-
verb
529-
.skip(template(keyword("predicate")))
530-
.skip(lookAhead(semicolon))
531-
.map((verb) => ({
532-
...verb,
533-
type: "verb",
534-
directObject: null,
535-
indirectObject: [],
536-
forObject: false,
537-
predicateType: "verb",
538-
})),
539-
word
540-
.skip(tag(sequence(keyword("v"), keyword("modal"))))
541-
.skip(template(keyword("predicate")))
542-
.skip(lookAhead(semicolon))
543-
.map((verb) => ({
544-
type: "modal verb",
545-
verb,
546-
})),
547-
linkingVerb
548-
.skip(template(keyword("predicate")))
549-
.skip(lookAhead(semicolon))
550-
.map((verb) => ({
551-
...verb,
552-
type: "verb",
553-
directObject: null,
554-
indirectObject: [],
555-
forObject: false,
556-
predicateType: "noun adjective",
557-
})),
558-
);
480+
)
481+
.map<PartialVerb>(([directObject, rawIndirectObject]) => {
482+
if (rawIndirectObject == null) {
483+
return {
484+
directObject,
485+
indirectObject: [],
486+
forObject: false,
487+
predicateType: null,
488+
};
489+
} else {
490+
const [preposition, indirectObject] = rawIndirectObject;
491+
if (indirectObject === "template") {
492+
return {
493+
directObject,
494+
indirectObject: [],
495+
forObject: preposition,
496+
predicateType: null,
497+
};
498+
} else {
499+
return {
500+
directObject,
501+
indirectObject: [{
502+
preposition,
503+
object: indirectObject,
504+
}],
505+
forObject: false,
506+
predicateType: null,
507+
};
508+
}
509+
}
510+
}),
511+
),
512+
)
513+
.map<Definition>(([[verb, forms], rest]) => {
514+
if (rest == null) {
515+
if (forms != null) {
516+
throw new ArrayResultError("modal verbs shouldn't be conjugated");
517+
}
518+
return { type: "modal verb", verb: escapeHtml(verb) };
519+
} else {
520+
let presentPlural: string;
521+
let presentSingular: string;
522+
let past: string;
523+
if (forms == null) {
524+
const sentence = nlp(verb);
525+
sentence.tag("Verb");
526+
const conjugations = sentence.verbs().conjugate()[0] as undefined | {
527+
Infinitive: string;
528+
PastTense: string;
529+
PresentTense: string;
530+
Gerund: string;
531+
FutureTense: string;
532+
};
533+
if (conjugations == null) {
534+
throw new ArrayResultError(
535+
`no verb conjugation found for "${verb}". consider providing ` +
536+
"all conjugations instead",
537+
);
538+
}
539+
if (verb !== conjugations.Infinitive) {
540+
throw new ArrayResultError(
541+
`conjugation error: "${verb}" is not ` +
542+
`"${conjugations.Infinitive}". consider providing all ` +
543+
"conjugations instead",
544+
);
545+
}
546+
presentPlural = escapeHtml(conjugations.Infinitive);
547+
presentSingular = escapeHtml(conjugations.PresentTense);
548+
past = escapeHtml(conjugations.PastTense);
549+
} else {
550+
presentPlural = escapeHtml(verb);
551+
[presentSingular, past] = forms;
552+
}
553+
return { ...rest, type: "verb", presentPlural, presentSingular, past };
554+
}
555+
});
559556
const definition = choiceWithCheck<Definition>(
560557
interjectionDefinition,
561558
particleDefinition,
@@ -572,7 +569,7 @@ const definition = choiceWithCheck<Definition>(
572569
adjective.map((adjective) => ({ ...adjective, type: "adjective" })),
573570
adverbDefinition,
574571
determiner.map((determiner) => ({ ...determiner, type: "determiner" })),
575-
checkedAsWhole(verbDefinition),
572+
verbDefinition,
576573
);
577574
const head = sequence(all(tokiPonaWord.skip(comma)), tokiPonaWord)
578575
.skip(colon)

0 commit comments

Comments
 (0)