Skip to content

Commit 22cff3b

Browse files
committed
rewrite grammar fixer
1 parent d139358 commit 22cff3b

1 file changed

Lines changed: 307 additions & 0 deletions

File tree

src/translator/fixer.ts

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
import { zip } from "@std/collections/zip";
2+
import { mapNullable } from "../misc/misc.ts";
3+
import * as English from "./ast.ts";
4+
import { FilteredError } from "./error.ts";
5+
import * as Dictionary from "../dictionary/type.ts";
6+
7+
function fixNounPhrase(noun: English.NounPhrase): English.NounPhrase {
8+
switch (noun.type) {
9+
case "simple":
10+
if (noun.adjectiveName != null && noun.prepositions.length > 0) {
11+
throw new FilteredError("named noun with preposition");
12+
}
13+
if (
14+
noun.postCompound != null && noun.postCompound.type === "simple" &&
15+
noun.postCompound.prepositions.length > 0
16+
) {
17+
new FilteredError("preposition within compound phrase");
18+
}
19+
return {
20+
...noun,
21+
determiners: fixMultipleDeterminers(noun.determiners),
22+
adjectives: fixMultipleAdjectives(noun.adjectives),
23+
postCompound: mapNullable(noun.postCompound, fixNounPhrase),
24+
prepositions: fixMultiplePrepositions(noun.prepositions),
25+
};
26+
case "compound":
27+
return {
28+
...noun,
29+
nouns: noun.nouns.map(fixNounPhrase),
30+
};
31+
}
32+
}
33+
function filterKind(
34+
determiners: ReadonlyArray<English.Determiner>,
35+
kinds: ReadonlyArray<Dictionary.DeterminerType>,
36+
) {
37+
return determiners.filter(({ kind }) => kinds.includes(kind));
38+
}
39+
function fixMultipleDeterminers(
40+
determiners: ReadonlyArray<English.Determiner>,
41+
) {
42+
const negatives = filterKind(determiners, ["negative"]);
43+
const first = filterKind(determiners, [
44+
"article",
45+
"demonstrative",
46+
"possessive",
47+
]);
48+
const articles = filterKind(determiners, ["article"]);
49+
const demonstratives = filterKind(determiners, ["demonstrative"]);
50+
const possessives = filterKind(determiners, ["possessive"]);
51+
const distributiveDeterminers = filterKind(determiners, ["distributive"]);
52+
const interrogatives = filterKind(determiners, ["interrogative"]);
53+
const quantitativeDeterminers = filterKind(determiners, [
54+
"numeral",
55+
"quantifier",
56+
]);
57+
const errors = filterSet([
58+
[
59+
negatives.length > 1,
60+
encodeDeterminer`multiple negative determiners ${negatives}`,
61+
],
62+
[articles.length > 1, encodeDeterminer`multiple articles ${articles}`],
63+
[
64+
demonstratives.length > 1,
65+
encodeDeterminer`multiple demonstrative determiners ${demonstratives}`,
66+
],
67+
[
68+
possessives.length > 1,
69+
encodeDeterminer`multiple possessive determiners ${possessives}`,
70+
],
71+
[
72+
distributiveDeterminers.length > 1,
73+
encodeDeterminer`multiple distributive determiners ${distributiveDeterminers}`,
74+
],
75+
[
76+
interrogatives.length > 1,
77+
encodeDeterminer`multiple interrogative determiners ${interrogatives}`,
78+
],
79+
[
80+
quantitativeDeterminers.length > 1,
81+
encodeDeterminer`multiple quantitative determiners ${quantitativeDeterminers}`,
82+
],
83+
[
84+
articles.length > 0 && demonstratives.length > 0,
85+
encodeDeterminer`article ${articles} with demonstrative determiner ${demonstratives}`,
86+
],
87+
[
88+
articles.length > 0 && possessives.length > 0,
89+
encodeDeterminer`article ${articles} with possessive determiner ${possessives}`,
90+
],
91+
[
92+
demonstratives.length > 0 && possessives.length > 0,
93+
encodeDeterminer`demonstrative determiner ${demonstratives} with possessive determiner ${possessives}`,
94+
],
95+
[
96+
negatives.length > 0 && interrogatives.length > 0,
97+
encodeDeterminer`negative determiner ${negatives} with interrogative determiner ${interrogatives}`,
98+
],
99+
]);
100+
if (errors.length === 0) {
101+
return [
102+
...negatives,
103+
...first,
104+
...distributiveDeterminers,
105+
...interrogatives,
106+
...quantitativeDeterminers,
107+
];
108+
} else {
109+
throw new AggregateError(
110+
errors.map((message) => new FilteredError(message())),
111+
);
112+
}
113+
}
114+
function fixAdjectivePhrase(
115+
adjective: English.AdjectivePhrase,
116+
): English.AdjectivePhrase {
117+
switch (adjective.type) {
118+
case "simple":
119+
return { ...adjective, adverbs: fixMultipleAdverbs(adjective.adverbs) };
120+
case "compound":
121+
return {
122+
...adjective,
123+
adjectives: adjective.adjectives.map(fixAdjectivePhrase),
124+
};
125+
}
126+
}
127+
function rankAdjective(kind: Dictionary.AdjectiveType) {
128+
return [
129+
"opinion",
130+
"size",
131+
"physical quality",
132+
"age",
133+
"color",
134+
"origin",
135+
"material",
136+
"qualifier",
137+
]
138+
.indexOf(kind);
139+
}
140+
function flattenAdjective(
141+
adjective: English.AdjectivePhrase,
142+
): ReadonlyArray<English.AdjectivePhrase & { type: "simple" }> {
143+
switch (adjective.type) {
144+
case "simple":
145+
return [adjective];
146+
case "compound":
147+
if (adjective.conjunction === "and") {
148+
return adjective.adjectives.flatMap(flattenAdjective);
149+
} else {
150+
// This should be unreachable
151+
throw new FilteredError(
152+
`${adjective.conjunction} within a string of adjective`,
153+
);
154+
}
155+
}
156+
}
157+
function fixMultipleAdjectives(
158+
adjectives: ReadonlyArray<English.AdjectivePhrase>,
159+
) {
160+
return adjectives
161+
.map(fixAdjectivePhrase)
162+
.flatMap(flattenAdjective)
163+
.sort((a, b) => rankAdjective(a.kind) - rankAdjective(b.kind));
164+
}
165+
function fixMultipleAdverbs(adverbs: ReadonlyArray<English.Adverb>) {
166+
if (adverbs.length > 1) {
167+
throw new FilteredError("multiple adverbs");
168+
} else {
169+
return adverbs;
170+
}
171+
}
172+
function fixComplement(complement: English.Complement): English.Complement {
173+
switch (complement.type) {
174+
case "noun":
175+
return { type: "noun", noun: fixNounPhrase(complement.noun) };
176+
case "adjective":
177+
return {
178+
type: "adjective",
179+
adjective: fixAdjectivePhrase(complement.adjective),
180+
};
181+
}
182+
}
183+
function fixAdverbVerb(adverbVerb: English.AdverbVerb): English.AdverbVerb {
184+
return {
185+
...adverbVerb,
186+
preAdverbs: fixMultipleAdverbs(adverbVerb.preAdverbs),
187+
};
188+
}
189+
function fixMultipleVerb(
190+
verb: ReadonlyArray<English.AdverbVerb>,
191+
): ReadonlyArray<English.AdverbVerb> {
192+
return verb.map(fixAdverbVerb);
193+
}
194+
function fixVerbPhrase(verb: English.VerbPhrase): English.VerbPhrase {
195+
switch (verb.type) {
196+
case "simple":
197+
return {
198+
...verb,
199+
verb: fixMultipleVerb(verb.verb),
200+
subjectComplement: mapNullable(verb.subjectComplement, fixComplement),
201+
contentClause: mapNullable(verb.contentClause, fixClause),
202+
object: mapNullable(verb.object, fixNounPhrase),
203+
objectComplement: mapNullable(verb.objectComplement, fixComplement),
204+
prepositions: fixMultiplePrepositions(verb.prepositions),
205+
};
206+
case "compound":
207+
return {
208+
...verb,
209+
verbs: verb.verbs.map(fixVerbPhrase),
210+
object: mapNullable(verb.object, fixNounPhrase),
211+
objectComplement: mapNullable(verb.objectComplement, fixComplement),
212+
prepositions: fixMultiplePrepositions(verb.prepositions),
213+
};
214+
}
215+
}
216+
function nounHasPreposition(noun: English.NounPhrase): boolean {
217+
switch (noun.type) {
218+
case "simple":
219+
return noun.prepositions.length > 0 ||
220+
(noun.postCompound != null && nounHasPreposition(noun.postCompound));
221+
case "compound":
222+
return noun.nouns.some(nounHasPreposition);
223+
}
224+
}
225+
function fixPreposition(
226+
preposition: English.Preposition,
227+
): English.Preposition {
228+
if (nounHasPreposition(preposition.object)) {
229+
throw new FilteredError("nested preposition");
230+
}
231+
return {
232+
...preposition,
233+
adverbs: fixMultipleAdverbs(preposition.adverbs),
234+
object: fixNounPhrase(preposition.object),
235+
};
236+
}
237+
function fixMultiplePrepositions(
238+
prepositions: ReadonlyArray<English.Preposition>,
239+
): ReadonlyArray<English.Preposition> {
240+
if (
241+
prepositions.filter((preposition) => preposition.preposition.word === "of")
242+
.length > 1
243+
) {
244+
throw new FilteredError('multiple "of"');
245+
} else {
246+
return prepositions.map(fixPreposition);
247+
}
248+
}
249+
function fixClause(clause: English.Clause): English.Clause {
250+
switch (clause.type) {
251+
case "simple":
252+
return {
253+
...clause,
254+
subject: fixNounPhrase(clause.subject),
255+
verb: fixVerbPhrase(clause.verb),
256+
};
257+
case "subject phrase":
258+
return { type: "subject phrase", subject: fixNounPhrase(clause.subject) };
259+
case "preposition":
260+
return { ...fixPreposition(clause), type: "preposition" };
261+
case "vocative":
262+
return { ...clause, addressee: fixNounPhrase(clause.addressee) };
263+
case "dependent":
264+
return { ...clause, clause: fixClause(clause.clause) };
265+
case "adverb":
266+
case "interjection":
267+
return clause;
268+
}
269+
}
270+
function fixSentence(
271+
{ clauses, punctuation }: English.Sentence,
272+
): English.Sentence {
273+
return { clauses: clauses.map(fixClause), punctuation };
274+
}
275+
export function fixMultipleSentences(
276+
sentence: English.MultipleSentences,
277+
): English.MultipleSentences {
278+
switch (sentence.type) {
279+
case "free form":
280+
return sentence;
281+
case "sentences":
282+
return {
283+
type: "sentences",
284+
sentences: sentence.sentences.map(fixSentence),
285+
};
286+
}
287+
}
288+
function filterSet<T>(
289+
set: ReadonlyArray<readonly [condition: boolean, value: T]>,
290+
) {
291+
return set.filter(([condition]) => condition).map(([_, value]) => value);
292+
}
293+
export function encodeDeterminer(
294+
strings: TemplateStringsArray,
295+
...determiners: ReadonlyArray<ReadonlyArray<English.Determiner>>
296+
): () => string {
297+
return () =>
298+
zip(strings, [
299+
...determiners
300+
.map((determiners) =>
301+
`(${determiners.map(({ determiner }) => determiner).join(" ")})`
302+
),
303+
"",
304+
])
305+
.flat()
306+
.join("");
307+
}

0 commit comments

Comments
 (0)