@fpw/en-wiktionary-la-modules 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +75 -75
  2. package/README.md +19 -15
  3. package/dist/LaEngine.d.ts +23 -23
  4. package/dist/LaEngine.js +64 -66
  5. package/dist/index.d.ts +12 -11
  6. package/dist/index.js +12 -28
  7. package/dist/modules/common.d.ts +67 -66
  8. package/dist/modules/common.js +145 -157
  9. package/dist/modules/conjugation/LaVerb.d.ts +118 -109
  10. package/dist/modules/conjugation/LaVerb.js +2861 -2295
  11. package/dist/modules/conjugation/VerbAffix.d.ts +18 -18
  12. package/dist/modules/conjugation/VerbAffix.js +19 -23
  13. package/dist/modules/conjugation/VerbForm.d.ts +204 -182
  14. package/dist/modules/conjugation/VerbForm.js +215 -199
  15. package/dist/modules/conjugation/VerbType.d.ts +54 -40
  16. package/dist/modules/conjugation/VerbType.js +66 -58
  17. package/dist/modules/declination/LaAdjData.d.ts +16 -16
  18. package/dist/modules/declination/LaAdjData.js +1474 -908
  19. package/dist/modules/declination/LaNominal.d.ts +136 -130
  20. package/dist/modules/declination/LaNominal.js +1884 -1804
  21. package/dist/modules/declination/LaNounData.d.ts +16 -2
  22. package/dist/modules/declination/LaNounData.js +935 -855
  23. package/dist/modules/declination/LaPersonalPronoun.d.ts +12 -12
  24. package/dist/modules/declination/LaPersonalPronoun.js +80 -85
  25. package/dist/modules/declination/NominalForm.d.ts +85 -69
  26. package/dist/modules/declination/NominalForm.js +101 -91
  27. package/dist/modules/declination/NominalType.d.ts +191 -120
  28. package/dist/modules/declination/NominalType.js +211 -146
  29. package/dist/modules/headword/HeadWord.d.ts +107 -107
  30. package/dist/modules/headword/HeadWord.js +28 -32
  31. package/dist/modules/headword/HeadwordParser.d.ts +29 -29
  32. package/dist/modules/headword/HeadwordParser.js +456 -452
  33. package/package.json +23 -15
  34. package/dist/LaEngine.js.map +0 -1
  35. package/dist/index.js.map +0 -1
  36. package/dist/modules/common.js.map +0 -1
  37. package/dist/modules/conjugation/LaVerb.js.map +0 -1
  38. package/dist/modules/conjugation/VerbAffix.js.map +0 -1
  39. package/dist/modules/conjugation/VerbForm.js.map +0 -1
  40. package/dist/modules/conjugation/VerbType.js.map +0 -1
  41. package/dist/modules/declination/LaAdjData.js.map +0 -1
  42. package/dist/modules/declination/LaNominal.js.map +0 -1
  43. package/dist/modules/declination/LaNounData.js.map +0 -1
  44. package/dist/modules/declination/LaPersonalPronoun.js.map +0 -1
  45. package/dist/modules/declination/NominalForm.js.map +0 -1
  46. package/dist/modules/declination/NominalType.js.map +0 -1
  47. package/dist/modules/headword/HeadWord.js.map +0 -1
  48. package/dist/modules/headword/HeadwordParser.js.map +0 -1
  49. package/src/LaEngine.ts +0 -86
  50. package/src/index.ts +0 -16
  51. package/src/modules/common.ts +0 -164
  52. package/src/modules/conjugation/LaVerb.ts +0 -2669
  53. package/src/modules/conjugation/VerbAffix.ts +0 -18
  54. package/src/modules/conjugation/VerbForm.ts +0 -223
  55. package/src/modules/conjugation/VerbType.ts +0 -55
  56. package/src/modules/declination/LaAdjData.ts +0 -1036
  57. package/src/modules/declination/LaNominal.ts +0 -2025
  58. package/src/modules/declination/LaNounData.ts +0 -897
  59. package/src/modules/declination/LaPersonalPronoun.ts +0 -92
  60. package/src/modules/declination/NominalForm.ts +0 -89
  61. package/src/modules/declination/NominalType.ts +0 -157
  62. package/src/modules/headword/HeadWord.ts +0 -132
  63. package/src/modules/headword/HeadwordParser.ts +0 -515
@@ -1,1804 +1,1884 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LaNominal = exports.NumberTantum = exports.Gender = void 0;
4
- /**
5
- * This is a complete re-implementation of Wiktionary's Module:la-nominal, developed by Benwing2.
6
- * It was converted from Lua to TypeScript by Folke Will <folko@solhost.org>.
7
- *
8
- * Original source: https://en.wiktionary.org/wiki/Module:la-nominal
9
- * Based on version: https://en.wiktionary.org/w/index.php?title=Module:la-nominal&oldid=68766664
10
- *
11
- * Lua idioms, function and variable names kept as in the original in order to easily
12
- * backport later changes to this implementation.
13
- *
14
- * For that reason, it's suggested to add a type-aware wrapper around this class and leave
15
- * this code unchanged instead of improving the types and use of idioms in this class.
16
- *
17
- */
18
- const common_1 = require("../common");
19
- const LaAdjData_1 = require("./LaAdjData");
20
- const LaNounData_1 = require("./LaNounData");
21
- const NominalForm_1 = require("./NominalForm");
22
- const NominalType_1 = require("./NominalType");
23
- var Gender;
24
- (function (Gender) {
25
- Gender["M"] = "M";
26
- Gender["F"] = "F";
27
- Gender["N"] = "N";
28
- })(Gender = exports.Gender || (exports.Gender = {}));
29
- var NumberTantum;
30
- (function (NumberTantum) {
31
- NumberTantum["Singular"] = "sg";
32
- NumberTantum["Plural"] = "pl";
33
- NumberTantum["Both"] = "both";
34
- })(NumberTantum = exports.NumberTantum || (exports.NumberTantum = {}));
35
- class LaNominal {
36
- constructor(options) {
37
- this.cases = ["nom", "gen", "dat", "acc", "abl", "voc", "loc"];
38
- this.genders = ["m", "f", "n"];
39
- this.nums = ["sg", "pl"];
40
- this.linked_prefixes = ["", "linked_"];
41
- this.potential_noun_lemma_slots = ["nom_sg", "nom_pl"];
42
- this.potential_adj_lemma_slots = [
43
- "nom_sg_m",
44
- "nom_pl_m",
45
- "nom_sg_f",
46
- "nom_pl_f",
47
- "nom_sg_n",
48
- "nom_pl_n"
49
- ];
50
- this.irreg_adj_to_decl = new Map([
51
- ["duo", "irreg+"],
52
- ["ambō", "irreg+"],
53
- ["mīlle", "3-1+"],
54
- ["plūs", "3-1+"],
55
- ["is", "1&2+"],
56
- ["īdem", "1&2+"],
57
- ["ille", "1&2+"],
58
- ["ipse", "1&2+"],
59
- ["iste", "1&2+"],
60
- ["quis", "irreg+"],
61
- ["quī", "irreg+"],
62
- ["quisquis", "irreg+"],
63
- ]);
64
- this.irreg_noun_to_decl = new Map([
65
- ["bōs", "3"],
66
- ["cherub", "irreg"],
67
- ["deus", "2"],
68
- ["Deus", "2"],
69
- ["domus", "4,2"],
70
- ["Iēsus", "4"],
71
- ["Jēsus", "4"],
72
- ["Iēsūs", "4"],
73
- ["Jēsūs", "4"],
74
- ["iūgerum", "2,3"],
75
- ["jūgerum", "2,3"],
76
- ["sūs", "3"],
77
- ["ēthos", "3"],
78
- ["Athōs", "2"],
79
- ["lexis", "3"],
80
- ["vēnum", "4,2"],
81
- ["vīs", "3"],
82
- ]);
83
- this.declension_to_english = new Map([
84
- ["1", "first"],
85
- ["2", "second"],
86
- ["3", "third"],
87
- ["4", "fourth"],
88
- ["5", "fifth"],
89
- ]);
90
- this.options = options || {};
91
- }
92
- do_generate_noun_forms(args, pos = "nouns", from_headword = false, title) {
93
- var _a;
94
- this.title = title;
95
- const parsed_run = this.parse_segment_run_allowing_alternants(((_a = args.get("1")) === null || _a === void 0 ? void 0 : _a.trim()) || "");
96
- parsed_run.loc = parsed_run.loc || args.has("loc_sg") || args.has("loc_pl");
97
- let num = args.get("num");
98
- if (num !== undefined && !(0, common_1.is_enum_value)(NumberTantum, num)) {
99
- num = undefined;
100
- }
101
- parsed_run.num = num || parsed_run.num;
102
- const declensions = this.decline_segment_run(parsed_run, pos, false);
103
- if (!parsed_run.loc) {
104
- (0, NominalForm_1.setNominalForm)(declensions.forms, "loc_sg", undefined);
105
- (0, NominalForm_1.setNominalForm)(declensions.forms, "loc_pl", undefined);
106
- }
107
- declensions.title = [this.construct_title(args.get("title"), declensions.title.join(""), false, parsed_run)];
108
- const all_data = {
109
- templateType: "declension",
110
- declensionType: "noun",
111
- title: declensions.title.join(" "),
112
- num: parsed_run.num,
113
- gender: parsed_run.gender,
114
- propses: parsed_run.propses,
115
- forms: declensions.forms,
116
- categories: declensions.categories,
117
- notes: new Map(),
118
- user_specified: new Set(),
119
- pos: pos,
120
- num_type: args.get("type"),
121
- // only if headword
122
- indecl: args.has("indecl"),
123
- m: (0, common_1.read_list)(args, "m"),
124
- f: (0, common_1.read_list)(args, "f"),
125
- overriding_lemma: (0, common_1.read_list)(args, "lemma"),
126
- overriding_genders: (0, common_1.read_list)(args, "g")
127
- };
128
- for (const slot of this.iter_noun_slots()) {
129
- const noteses = declensions.notes.get(slot);
130
- if (noteses) {
131
- noteses.forEach((notes, index) => {
132
- all_data.notes.set(`${slot}${index + 1}`, notes);
133
- });
134
- }
135
- }
136
- this.process_noun_forms_and_overrides(all_data, args);
137
- return all_data;
138
- }
139
- do_generate_adj_forms(args, pos = "adjectives", from_headword = false, title) {
140
- var _a;
141
- this.title = title;
142
- let segment_run = ((_a = args.get("1")) === null || _a === void 0 ? void 0 : _a.trim()) || "";
143
- if (!segment_run.match(/[<(]/)) {
144
- segment_run = segment_run + (args.has("indecl") ? "<0+>" : "<+>");
145
- }
146
- const parsed_run = this.parse_segment_run_allowing_alternants(segment_run);
147
- parsed_run.loc = parsed_run.loc || (args.has("loc_sg_m") || args.has("loc_sg_f") || args.has("loc_sg_n") || args.has("loc_pl_m") || args.has("loc_pl_f") || args.has("loc_pl_n"));
148
- let num = args.get("num");
149
- if (num !== undefined && !(0, common_1.is_enum_value)(NumberTantum, num)) {
150
- num = undefined;
151
- }
152
- parsed_run.num = num || parsed_run.num;
153
- const overriding_voc = (args.has("voc_sg_m") || args.has("voc_sg_f") || args.has("voc_sg_n") || args.has("voc_pl_m") || args.has("voc_pl_f") || args.has("voc_pl_n"));
154
- const declensions = this.decline_segment_run(parsed_run, pos, true);
155
- if (!parsed_run.loc) {
156
- (0, NominalForm_1.setNominalForm)(declensions.forms, "loc_sg_m", undefined);
157
- (0, NominalForm_1.setNominalForm)(declensions.forms, "loc_sg_f", undefined);
158
- (0, NominalForm_1.setNominalForm)(declensions.forms, "loc_sg_n", undefined);
159
- (0, NominalForm_1.setNominalForm)(declensions.forms, "loc_pl_m", undefined);
160
- (0, NominalForm_1.setNominalForm)(declensions.forms, "loc_pl_f", undefined);
161
- (0, NominalForm_1.setNominalForm)(declensions.forms, "loc_pl_n", undefined);
162
- }
163
- if (!overriding_voc && !declensions.voc) {
164
- (0, NominalForm_1.setNominalForm)(declensions.forms, "voc_sg_m", undefined);
165
- (0, NominalForm_1.setNominalForm)(declensions.forms, "voc_sg_f", undefined);
166
- (0, NominalForm_1.setNominalForm)(declensions.forms, "voc_sg_n", undefined);
167
- (0, NominalForm_1.setNominalForm)(declensions.forms, "voc_pl_m", undefined);
168
- (0, NominalForm_1.setNominalForm)(declensions.forms, "voc_pl_f", undefined);
169
- (0, NominalForm_1.setNominalForm)(declensions.forms, "voc_pl_n", undefined);
170
- }
171
- declensions.title = [this.construct_title(args.get("title"), declensions.title.join(""), from_headword, parsed_run)];
172
- const all_data = {
173
- templateType: "declension",
174
- declensionType: "adjective",
175
- title: declensions.title.join(""),
176
- num: parsed_run.num,
177
- propses: parsed_run.propses,
178
- forms: declensions.forms,
179
- categories: declensions.categories,
180
- notes: new Map(),
181
- user_specified: new Set(),
182
- voc: declensions.voc,
183
- noneut: args.has("noneut") || declensions.noneut,
184
- nomf: args.has("nomf") || declensions.nomf,
185
- pos: pos,
186
- num_type: args.get("type"),
187
- // only if headword
188
- overriding_lemma: (0, common_1.read_list)(args, "lemma"),
189
- indecl: args.has("indecl"),
190
- comp: (0, common_1.read_list)(args, "comp"),
191
- sup: (0, common_1.read_list)(args, "sup"),
192
- adv: (0, common_1.read_list)(args, "adv")
193
- };
194
- for (const slot of this.iter_adj_slots()) {
195
- const noteses = declensions.notes.get(slot);
196
- if (noteses) {
197
- noteses.forEach((notes, index) => {
198
- all_data.notes.set(`${slot}${index + 1}`, notes);
199
- });
200
- }
201
- }
202
- this.process_adj_forms_and_overrides(all_data, args);
203
- return all_data;
204
- }
205
- construct_title(args_title, declensions_title, from_headword, parsed_run) {
206
- if (args_title) {
207
- declensions_title = args_title.replace("<1>", "first declension");
208
- declensions_title = declensions_title.replace("<1&2>", "first/second declension");
209
- declensions_title = declensions_title.replace("<2>", "second declension");
210
- declensions_title = declensions_title.replace("<3>", "third declension");
211
- declensions_title = declensions_title.replace("<4>", "fourth declension");
212
- declensions_title = declensions_title.replace("<5>", "fifth declension");
213
- if (from_headword) {
214
- declensions_title = declensions_title[0].toLowerCase() + declensions_title.replace(/\.$/, "").substr(1);
215
- }
216
- else {
217
- if (declensions_title.startsWith(" ")) {
218
- declensions_title = declensions_title.substr(1);
219
- }
220
- declensions_title = declensions_title[0].toUpperCase() + declensions_title.substr(1);
221
- }
222
- }
223
- else {
224
- const post_text_parts = [];
225
- if (parsed_run.loc) {
226
- post_text_parts.push(", with locative");
227
- }
228
- if (parsed_run.num == "sg") {
229
- post_text_parts.push(", singular only");
230
- }
231
- else if (parsed_run.num == "pl") {
232
- post_text_parts.push(", plural only");
233
- }
234
- const post_text = post_text_parts.join("");
235
- if (from_headword) {
236
- declensions_title = declensions_title[0].toLowerCase() + declensions_title.substr(1) + post_text;
237
- }
238
- else {
239
- if (declensions_title.length > 0) {
240
- declensions_title = declensions_title[0].toUpperCase() + declensions_title.substr(1) + post_text + ".";
241
- }
242
- }
243
- }
244
- return declensions_title;
245
- }
246
- process_noun_forms_and_overrides(data, args) {
247
- var _a, _b;
248
- const linked_to_non_linked_noun_slots = new Map();
249
- for (const slot of this.potential_noun_lemma_slots) {
250
- linked_to_non_linked_noun_slots.set("linked_" + slot, slot);
251
- }
252
- for (const slot of this.iter_noun_slots()) {
253
- let val = [];
254
- if (args.has(slot)) {
255
- val = ((_a = args.get(slot)) === null || _a === void 0 ? void 0 : _a.split("/")) || [];
256
- data.user_specified.add(slot);
257
- }
258
- else {
259
- const non_linked_equiv_slot = linked_to_non_linked_noun_slots.get(slot);
260
- if (non_linked_equiv_slot && args.has(non_linked_equiv_slot)) {
261
- val = ((_b = args.get(non_linked_equiv_slot)) === null || _b === void 0 ? void 0 : _b.split("/")) || [];
262
- data.user_specified.add(slot);
263
- }
264
- else {
265
- val = (0, NominalForm_1.getNominalForm)(data.forms, slot) || [];
266
- }
267
- }
268
- if (val) {
269
- if ((data.num == "pl" && slot.includes("sg")) || (data.num == "sg" && slot.includes("pl"))) {
270
- (0, NominalForm_1.setNominalForm)(data.forms, slot, [""]);
271
- }
272
- else if (val[0] == "" || val[0] == "-" || val[0] == "—") {
273
- (0, NominalForm_1.setNominalForm)(data.forms, slot, [LaNominal.EmptyForm]);
274
- }
275
- else {
276
- (0, NominalForm_1.setNominalForm)(data.forms, slot, val);
277
- }
278
- }
279
- }
280
- }
281
- process_adj_forms_and_overrides(data, args) {
282
- var _a;
283
- const linked_to_non_linked_adj_slots = new Map();
284
- for (const slot of this.potential_adj_lemma_slots) {
285
- linked_to_non_linked_adj_slots.set("linked_" + slot, slot);
286
- }
287
- for (const slot of this.iter_adj_slots()) {
288
- if (data.noneut && slot.match(/_n/)) {
289
- (0, NominalForm_1.setNominalForm)(data.forms, slot, undefined);
290
- }
291
- if (data.nomf && (slot.match(/_m/) || slot.match(/_f/))) {
292
- (0, NominalForm_1.setNominalForm)(data.forms, slot, undefined);
293
- }
294
- let val;
295
- const ovr = args.get(slot);
296
- if (ovr) {
297
- val = ovr.split("/");
298
- data.user_specified.add(slot);
299
- }
300
- else {
301
- const non_linked_equiv_slot = linked_to_non_linked_adj_slots.get(slot);
302
- if (non_linked_equiv_slot && args.has(non_linked_equiv_slot)) {
303
- val = ((_a = args.get(non_linked_equiv_slot)) === null || _a === void 0 ? void 0 : _a.split("/")) || [];
304
- data.user_specified.add(slot);
305
- }
306
- else {
307
- val = (0, NominalForm_1.getNominalForm)(data.forms, slot);
308
- }
309
- }
310
- if (val) {
311
- if ((data.num == "pl" && slot.match(/sg/)) || (data.num == "sg" && slot.match(/pl/))) {
312
- (0, NominalForm_1.setNominalForm)(data.forms, slot, undefined);
313
- }
314
- else if (val[0] == "" || val[0] == "-" || val[0] == "—") {
315
- (0, NominalForm_1.setNominalForm)(data.forms, slot, [LaNominal.EmptyForm]);
316
- }
317
- else {
318
- (0, NominalForm_1.setNominalForm)(data.forms, slot, val);
319
- }
320
- }
321
- }
322
- for (const gender of ["f", "n"]) {
323
- let other_is_masc = true;
324
- for (const cas of this.cases) {
325
- for (const num of this.nums) {
326
- const genderForm = (0, NominalForm_1.getNominalForm)(data.forms, cas + "_" + num + "_" + gender);
327
- const amscForm = (0, NominalForm_1.getNominalForm)(data.forms, cas + "_" + num + "_m");
328
- if (!(0, common_1.array_equals)(genderForm, amscForm)) {
329
- other_is_masc = false;
330
- break;
331
- }
332
- }
333
- if (!other_is_masc) {
334
- break;
335
- }
336
- }
337
- if (other_is_masc && !this.options.populateAllTerminations) {
338
- for (const cas of this.cases) {
339
- for (const num of this.nums) {
340
- (0, NominalForm_1.setNominalForm)(data.forms, cas + "_" + num + "_" + gender, undefined);
341
- }
342
- }
343
- }
344
- }
345
- }
346
- iter_slots(is_adj, overridable_only = false) {
347
- if (is_adj) {
348
- return this.iter_adj_slots(overridable_only);
349
- }
350
- else {
351
- return this.iter_noun_slots(overridable_only);
352
- }
353
- }
354
- iter_adj_slots(overridable_only = false) {
355
- let cas = 1;
356
- let num = 1;
357
- let gen = 1;
358
- let linked_variant = 0;
359
- const entries = [];
360
- while (true) {
361
- linked_variant = linked_variant + 1;
362
- let max_linked_variant;
363
- if (overridable_only) {
364
- max_linked_variant = 1;
365
- }
366
- else {
367
- if (this.cases[cas - 1] == "nom" && this.genders[gen - 1] == "m") {
368
- max_linked_variant = 2;
369
- }
370
- else {
371
- max_linked_variant = 1;
372
- }
373
- }
374
- if (linked_variant > max_linked_variant) {
375
- linked_variant = 1;
376
- gen++;
377
- if (gen > this.genders.length) {
378
- gen = 1;
379
- num++;
380
- if (num > this.nums.length) {
381
- num = 1;
382
- cas++;
383
- if (cas > this.cases.length) {
384
- break;
385
- }
386
- }
387
- }
388
- }
389
- entries.push(this.linked_prefixes[linked_variant - 1] + this.cases[cas - 1] + "_" + this.nums[num - 1] + "_" + this.genders[gen - 1]);
390
- }
391
- return entries;
392
- }
393
- iter_noun_slots(overridable_only = false) {
394
- let cas = 1;
395
- let num = 1;
396
- let linked_variant = 0;
397
- const entries = [];
398
- while (true) {
399
- linked_variant = linked_variant + 1;
400
- let max_linked_variant = 1;
401
- if (!overridable_only) {
402
- if (this.cases[cas - 1] == "nom") {
403
- max_linked_variant = 2;
404
- }
405
- }
406
- if (linked_variant > max_linked_variant) {
407
- linked_variant = 1;
408
- num++;
409
- if (num > this.nums.length) {
410
- num = 1;
411
- cas++;
412
- if (cas > this.cases.length) {
413
- break;
414
- }
415
- }
416
- }
417
- entries.push(this.linked_prefixes[linked_variant - 1] + this.cases[cas - 1] + "_" + this.nums[num - 1]);
418
- }
419
- return entries;
420
- }
421
- parse_segment_run_allowing_alternants(segment_run) {
422
- const alternating_segments = this.capturing_split(segment_run, /(\(\(.*?\)\))/);
423
- const parsed_segments = [];
424
- let loc = false;
425
- let num;
426
- let gender;
427
- let is_adj;
428
- const propses = [];
429
- for (let i = 0; i < alternating_segments.length; i++) {
430
- const alternating_segment = alternating_segments[i];
431
- let this_is_adj;
432
- if (alternating_segment) {
433
- if (i % 2 == 0) {
434
- const parsed_run = this.parse_segment_run(alternating_segment);
435
- for (const parsed_segment of parsed_run.segments) {
436
- parsed_segments.push(parsed_segment);
437
- }
438
- loc = loc || parsed_run.loc;
439
- num = num || parsed_run.num;
440
- gender = gender || parsed_run.gender;
441
- this_is_adj = parsed_run.is_adj;
442
- for (const prop of parsed_run.propses) {
443
- propses.push(prop);
444
- }
445
- }
446
- else {
447
- const parsed_alternating_segment = this.parse_alternant(alternating_segment);
448
- parsed_segments.push(parsed_alternating_segment);
449
- loc = loc || parsed_alternating_segment.loc;
450
- num = num || parsed_alternating_segment.num;
451
- gender = gender || parsed_alternating_segment.gender;
452
- this_is_adj = parsed_alternating_segment.is_adj;
453
- propses.push(...parsed_alternating_segment.propses);
454
- }
455
- }
456
- if (is_adj === undefined) {
457
- is_adj = this_is_adj;
458
- }
459
- else if (this_is_adj !== undefined) {
460
- is_adj = is_adj && this_is_adj;
461
- }
462
- }
463
- return {
464
- segments: parsed_segments,
465
- loc: loc,
466
- num: num,
467
- gender: gender,
468
- is_adj: is_adj,
469
- propses: propses,
470
- };
471
- }
472
- parse_alternant(alternant) {
473
- const parsed_alternants = [];
474
- const alternant_spec = alternant.match(/^\(\((.*)\)\)$/);
475
- let loc = false;
476
- let num;
477
- let gender;
478
- let is_adj;
479
- const propses = [];
480
- if (!alternant_spec) {
481
- throw Error(`Invalid alternant spec`);
482
- }
483
- const alternants = alternant_spec[1].split(",");
484
- alternants.forEach((altr, i) => {
485
- const parsed_run = this.parse_segment_run(altr);
486
- parsed_alternants.push(parsed_run);
487
- loc = loc || parsed_run.loc;
488
- if (i == 0) {
489
- num = parsed_run.num;
490
- }
491
- else if (num != parsed_run.num) {
492
- num = NumberTantum.Both;
493
- }
494
- gender = gender || parsed_run.gender;
495
- if (is_adj === undefined) {
496
- is_adj = parsed_run.is_adj;
497
- }
498
- else if (parsed_run.is_adj !== undefined && parsed_run.is_adj !== is_adj) {
499
- throw Error(`Saw both noun and adjective alternants; not allowed`);
500
- }
501
- propses.push(...parsed_run.propses);
502
- });
503
- return {
504
- type: "Alternant",
505
- alternants: parsed_alternants,
506
- loc: loc,
507
- num: num,
508
- gender: gender,
509
- is_adj: is_adj,
510
- propses: propses,
511
- };
512
- }
513
- parse_segment_run(segment_run) {
514
- const is_suffix = segment_run.startsWith("-");
515
- const segments = [];
516
- const bracketed_segments = this.capturing_split(segment_run, /(\[\[[^\[\]]-\]\]<.*?>)/);
517
- bracketed_segments.forEach((bracketed_segment, i) => {
518
- if (i % 2 == 1) {
519
- segments.push(bracketed_segment);
520
- }
521
- else {
522
- let regex;
523
- if (is_suffix) {
524
- regex = /([^<> ,]+<.*?>)/;
525
- }
526
- else {
527
- regex = /([^<> ,\-]+<.*?>)/;
528
- }
529
- for (const subsegment of this.capturing_split(bracketed_segment, regex)) {
530
- segments.push(subsegment);
531
- }
532
- }
533
- });
534
- let loc = false;
535
- let num;
536
- let gender;
537
- let is_adj;
538
- const propses = [];
539
- const parsed_segments = [];
540
- for (let i = 1; i < segments.length; i += 2) {
541
- const parsed_segment = this.parse_segment(segments[i]);
542
- loc = loc || parsed_segment.loc;
543
- num = num || parsed_segment.num;
544
- if (is_adj === undefined) {
545
- is_adj = parsed_segment.is_adj;
546
- }
547
- else {
548
- is_adj = is_adj && parsed_segment.is_adj;
549
- }
550
- gender = gender || parsed_segment.gender;
551
- parsed_segment.orig_prefix = segments[i - 1];
552
- parsed_segment.prefix = (0, common_1.remove_links)(segments[i - 1]);
553
- parsed_segments.push(parsed_segment);
554
- const props = {
555
- decl: parsed_segment.decl,
556
- headword_decl: parsed_segment.headword_decl,
557
- types: parsed_segment.types,
558
- };
559
- propses.push(props);
560
- }
561
- if (segments[segments.length - 1]) {
562
- parsed_segments.push({
563
- type: "Segment",
564
- args: [],
565
- decl: "",
566
- headword_decl: "",
567
- is_adj: false,
568
- lemma: "",
569
- orig_lemma: "",
570
- loc: false,
571
- types: new Set(),
572
- orig_prefix: segments[segments.length - 1],
573
- prefix: (0, common_1.remove_links)(segments[segments.length - 1])
574
- });
575
- }
576
- return {
577
- segments: parsed_segments,
578
- loc: loc,
579
- num: num,
580
- gender: gender,
581
- propses: propses,
582
- };
583
- }
584
- capturing_split(str, pattern) {
585
- return str.split(pattern);
586
- }
587
- parse_segment(segment) {
588
- const match = segment.match(/^(.*)<(.*?)>$/);
589
- if (!match) {
590
- throw Error("No match");
591
- }
592
- const stem_part = match[1];
593
- const spec_part = match[2];
594
- const stems = stem_part.split("/");
595
- const specs = spec_part.split(".");
596
- const types = new Set();
597
- let num;
598
- let loc = false;
599
- let decl = "";
600
- for (let j = 0; j < specs.length; j++) {
601
- let spec = specs[j];
602
- if (j == 0) {
603
- decl = spec;
604
- }
605
- else {
606
- const m2 = spec.match(/^(-?)(.*?)$/);
607
- if (m2) {
608
- const begins_with_hypen = m2[1];
609
- spec = m2[2];
610
- spec = begins_with_hypen + spec.replace(/-/g, "_");
611
- if (spec) {
612
- (0, NominalType_1.addNominalType)(types, spec);
613
- }
614
- }
615
- }
616
- }
617
- const orig_lemma = stems[0] || this.title;
618
- if (!orig_lemma) {
619
- throw Error("No lemma");
620
- }
621
- const lemma = (0, common_1.remove_links)(orig_lemma);
622
- let stem2 = stems[1];
623
- if (stems.length > 2) {
624
- throw Error(`Too many stems, at most 2 should be give: ${stem_part}`);
625
- }
626
- let is_adj = false;
627
- let headword_decl;
628
- let base;
629
- let detected_subtypes;
630
- if (decl.match(/\+/)) {
631
- decl = decl.replace(/\+/g, "");
632
- [base, stem2, decl, detected_subtypes] = this.detect_adj_type_and_subtype(lemma, stem2, decl, types);
633
- is_adj = true;
634
- const irreg = this.irreg_adj_to_decl.get(lemma);
635
- if (irreg) {
636
- headword_decl = `irreg/${irreg}`;
637
- }
638
- else {
639
- headword_decl = decl + "+";
640
- }
641
- for (const subtype of detected_subtypes) {
642
- if ((0, NominalType_1.hasNominalType)(types, "-" + subtype)) {
643
- (0, NominalType_1.delNominalType)(types, "-" + subtype);
644
- }
645
- else {
646
- types.add(subtype);
647
- }
648
- }
649
- }
650
- else {
651
- [base, stem2, detected_subtypes] = this.detect_noun_subtype(lemma, stem2, decl, types);
652
- const irreg = this.irreg_noun_to_decl.get(lemma);
653
- if (irreg) {
654
- headword_decl = `irreg/${irreg}`;
655
- }
656
- else {
657
- headword_decl = decl;
658
- }
659
- for (const subtype of detected_subtypes) {
660
- if ((0, NominalType_1.hasNominalType)(types, "-" + subtype)) {
661
- (0, NominalType_1.delNominalType)(types, "-" + subtype);
662
- }
663
- else if ((subtype == NominalType_1.NominalType.Masculine || subtype == NominalType_1.NominalType.Feminine || subtype == NominalType_1.NominalType.Neuter) &&
664
- (types.has(NominalType_1.NominalType.Masculine) || types.has(NominalType_1.NominalType.Feminine) || types.has(NominalType_1.NominalType.Neuter))) {
665
- // don't create conflicting gender specs
666
- }
667
- else if ((subtype == NominalType_1.NominalType.Singular || subtype == NominalType_1.NominalType.Plural || subtype == NominalType_1.NominalType.Both) &&
668
- (types.has(NominalType_1.NominalType.Singular) || types.has(NominalType_1.NominalType.Plural) || types.has(NominalType_1.NominalType.Both))) {
669
- // don't create conflicting number restrictions
670
- }
671
- else {
672
- types.add(subtype);
673
- }
674
- }
675
- if (!types.has(NominalType_1.NominalType.Plural) && !types.has(NominalType_1.NominalType.Both) && lemma.match(/^[A-ZĀĒĪŌŪȲĂĔĬŎŬ]/)) {
676
- types.add(NominalType_1.NominalType.Singular);
677
- }
678
- }
679
- if (types.has(NominalType_1.NominalType.Locative)) {
680
- loc = true;
681
- types.delete(NominalType_1.NominalType.Locative);
682
- }
683
- let gender;
684
- if (types.has(NominalType_1.NominalType.Masculine)) {
685
- gender = Gender.M;
686
- }
687
- else if (types.has(NominalType_1.NominalType.Feminine)) {
688
- gender = Gender.F;
689
- }
690
- else if (types.has(NominalType_1.NominalType.Neuter)) {
691
- gender = Gender.N;
692
- }
693
- if (types.has(NominalType_1.NominalType.Plural)) {
694
- num = NumberTantum.Plural;
695
- types.delete(NominalType_1.NominalType.Plural);
696
- }
697
- else if (types.has(NominalType_1.NominalType.Singular)) {
698
- num = NumberTantum.Singular;
699
- types.delete(NominalType_1.NominalType.Singular);
700
- }
701
- const args = [base, stem2];
702
- return {
703
- type: "Segment",
704
- decl: decl,
705
- headword_decl: headword_decl,
706
- is_adj: is_adj,
707
- gender: gender,
708
- orig_lemma: orig_lemma,
709
- lemma: lemma,
710
- stem2: stem2,
711
- types: types,
712
- num: num,
713
- loc: loc,
714
- args: args,
715
- };
716
- }
717
- detect_adj_type_and_subtype(lemma, stem2, typ, subtypes) {
718
- if (!typ.match(/^[0123]/) && !typ.match(/^irreg/) && typ) {
719
- subtypes = new Set(subtypes);
720
- (0, NominalType_1.addNominalType)(subtypes, typ);
721
- typ = "";
722
- }
723
- function base_as_stem2(base, stem2x) {
724
- return ["foo", base];
725
- }
726
- function constant_base(baseval) {
727
- return (base, s2) => [baseval, ""];
728
- }
729
- function decl12_stem2(base) {
730
- return base;
731
- }
732
- function decl3_stem2(base) {
733
- return LaNominal.make_stem2(base);
734
- }
735
- const decl12_entries = [
736
- ["us", "1&2", []],
737
- ["a", "1&2", []],
738
- ["um", "1&2", []],
739
- ["ī", "1&2", [NominalType_1.NominalType.Plural]],
740
- ["ae", "1&2", [NominalType_1.NominalType.Plural]],
741
- ["a", "1&2", [NominalType_1.NominalType.Plural]],
742
- ["os", "1&2", [NominalType_1.NominalType.GreekA, NominalType_1.NominalType.NoGreekE]],
743
- ["os", "1&2", [NominalType_1.NominalType.GreekE, NominalType_1.NominalType.NoGreekA]],
744
- ["ē", "1&2", [NominalType_1.NominalType.GreekE, NominalType_1.NominalType.NoGreekA]],
745
- ["on", "1&2", [NominalType_1.NominalType.GreekA, NominalType_1.NominalType.NoGreekE]],
746
- ["on", "1&2", [NominalType_1.NominalType.GreekE, NominalType_1.NominalType.NoGreekA]],
747
- ["^(.*er)$", "1&2", [NominalType_1.NominalType.Er]],
748
- ["^(.*ur)$", "1&2", [NominalType_1.NominalType.Er]],
749
- ["^(h)ic$", "1&2", [NominalType_1.NominalType.Ic]],
750
- ];
751
- const decl3_entries = [
752
- ["^(.*er)$", "3-3", []],
753
- ["is", "3-2", []],
754
- ["e", "3-2", []],
755
- ["^(.*[ij])or$", "3-C", []],
756
- ["^(min)or$", "3-C", []],
757
- ["^(.*ēs)$", "3-1", [NominalType_1.NominalType.I]],
758
- ["^(.*ēs)$", "3-1", [NominalType_1.NominalType.Par]],
759
- ["^(.*[ij])ōrēs$", "3-C", [NominalType_1.NominalType.Plural]],
760
- ["^(min)ōrēs$", "3-C", [NominalType_1.NominalType.Plural]],
761
- ["ēs", "3-2", [NominalType_1.NominalType.Plural, NominalType_1.NominalType.I]],
762
- ["ēs", "3-1", [NominalType_1.NominalType.Plural, NominalType_1.NominalType.Par], base_as_stem2],
763
- ["ia", "3-2", [NominalType_1.NominalType.Plural, NominalType_1.NominalType.I]],
764
- ["a", "3-1", [NominalType_1.NominalType.Plural, NominalType_1.NominalType.Par], base_as_stem2],
765
- ["", "3-1", [NominalType_1.NominalType.I]],
766
- ["", "3-1", [NominalType_1.NominalType.Par]],
767
- ];
768
- if (!typ) {
769
- const [base, new_stem2, rettype, new_subtypes] = this.get_adj_type_and_subtype_by_ending(lemma, stem2, undefined, subtypes, decl12_entries, decl12_stem2);
770
- if (base) {
771
- return [base, new_stem2, rettype, new_subtypes];
772
- }
773
- else {
774
- return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, decl3_entries, decl3_stem2);
775
- }
776
- }
777
- else if (typ == "0") {
778
- return [lemma, "", "0", []];
779
- }
780
- else if (typ == "3") {
781
- return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, decl3_entries, decl3_stem2);
782
- }
783
- else if (typ == "1&2") {
784
- return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, decl12_entries, decl12_stem2);
785
- }
786
- else if (typ == "1-1") {
787
- return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
788
- ["a", "1-1", []],
789
- ["ae", "1-1", [NominalType_1.NominalType.Plural]]
790
- ]);
791
- }
792
- else if (typ == "2-2") {
793
- return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
794
- ["us", "2-2", []],
795
- ["um", "2-2", []],
796
- ["ī", "2-2", [NominalType_1.NominalType.Plural]],
797
- ["a", "2-2", [NominalType_1.NominalType.Plural]],
798
- ["os", "2-2", [NominalType_1.NominalType.greek]],
799
- ["on", "2-2", [NominalType_1.NominalType.greek]],
800
- ["oe", "2-2", [NominalType_1.NominalType.greek, NominalType_1.NominalType.Plural]],
801
- ]);
802
- }
803
- else if (typ == "3-1") {
804
- return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
805
- ["^(.*ēs)$", "3-1", [NominalType_1.NominalType.I]],
806
- ["^(.*ēs)$", "3-1", [NominalType_1.NominalType.Par]],
807
- ["ēs", "3-1", [NominalType_1.NominalType.Plural, NominalType_1.NominalType.I], base_as_stem2],
808
- ["ēs", "3-1", [NominalType_1.NominalType.Plural, NominalType_1.NominalType.Par], base_as_stem2],
809
- ["ia", "3-1", [NominalType_1.NominalType.Plural, NominalType_1.NominalType.I], base_as_stem2],
810
- ["a", "3-1", [NominalType_1.NominalType.Plural, NominalType_1.NominalType.Par], base_as_stem2],
811
- ["", "3-1", [NominalType_1.NominalType.I]],
812
- ["", "3-1", [NominalType_1.NominalType.Par]],
813
- ], decl3_stem2);
814
- }
815
- else if (typ == "3-2") {
816
- return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
817
- ["is", "3-2", []],
818
- ["e", "3-2", []],
819
- ["ēs", "3-2", []],
820
- ["ēs", "3-2", [NominalType_1.NominalType.Plural]],
821
- ["ia", "3-2", [NominalType_1.NominalType.Plural]],
822
- ], decl3_stem2);
823
- }
824
- else if (typ == "3-C") {
825
- return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
826
- ["^(.*[ij])or$", "3-C", []],
827
- ["^(min)or$", "3-C", []],
828
- ["^(.*[ij])ōrēs$", "3-C", [NominalType_1.NominalType.Plural]],
829
- ["^(min)ōrēs$", "3-C", [NominalType_1.NominalType.Plural]],
830
- ], decl3_stem2);
831
- }
832
- else if (typ == "irreg") {
833
- return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
834
- ["^(duo)$", typ, [NominalType_1.NominalType.Plural]],
835
- ["^(ambō)$", typ, [NominalType_1.NominalType.Plural]],
836
- ["^(mīll?ia)$", typ, [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Plural], constant_base("mīlle")],
837
- ["^(ea)$", typ, [], constant_base("is")],
838
- ["^(id)$", typ, [], constant_base("is")],
839
- ["^([ei]ī)$", typ, [NominalType_1.NominalType.Plural], constant_base("is")],
840
- ["^(eae?)$", typ, [NominalType_1.NominalType.Plural], constant_base("is")],
841
- ["^(eadem)$", typ, [], constant_base("īdem")],
842
- ["^([īi]dem)$", typ, [], constant_base("īdem")],
843
- ["^(īdem)$", typ, [NominalType_1.NominalType.Plural]],
844
- ["^(eae?dem)$", typ, [NominalType_1.NominalType.Plural], constant_base("īdem")],
845
- ["^(i[lps][lst])a$", typ, [], (base, s2) => [base + "e", ""]],
846
- ["^(i[ls][lt])ud$", typ, [], (base, s2) => [base + "e", ""]],
847
- ["^(ipsum)$", typ, [], constant_base("ipse")],
848
- ["^(i[lps][lst])ī$", typ, [NominalType_1.NominalType.Plural], (base, s2) => [base + "e", ""]],
849
- ["^(i[lps][lst])ae?$", typ, [NominalType_1.NominalType.Plural], (base, s2) => [base + "e", ""]],
850
- ["^(quī)$", typ, []],
851
- ["^(quī)$", typ, [NominalType_1.NominalType.Plural]],
852
- ["^(quae)$", typ, [], constant_base("quī")],
853
- ["^(quae)$", typ, [NominalType_1.NominalType.Plural], constant_base("quī")],
854
- ["^(quid)$", typ, [], constant_base("quis")],
855
- ["^(quod)$", typ, [], constant_base("quī")],
856
- ["^(qui[cd]quid)$", typ, [], constant_base("quisquis")],
857
- ["^(quīquī)$", typ, [NominalType_1.NominalType.Plural], constant_base("quisquis")],
858
- ["^(quaequae)$", typ, [NominalType_1.NominalType.Plural], constant_base("quisquis")],
859
- ["", typ, []],
860
- ]);
861
- }
862
- else {
863
- return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
864
- ["ēs", typ, [NominalType_1.NominalType.Plural], base_as_stem2],
865
- ["ia", typ, [NominalType_1.NominalType.Plural], base_as_stem2],
866
- ["", typ, []],
867
- ], decl3_stem2);
868
- }
869
- }
870
- get_adj_type_and_subtype_by_ending(lemma, stem2, decltype, specified_subtypes, endings_and_subtypes, process_stem2) {
871
- for (const [ending, rettype, subtypes, process_retval] of endings_and_subtypes) {
872
- let not_this_subtype = false;
873
- if (specified_subtypes.has(NominalType_1.NominalType.Plural) && !subtypes.includes(NominalType_1.NominalType.Plural)) {
874
- not_this_subtype = true;
875
- }
876
- else {
877
- for (const subtype of subtypes) {
878
- if (!subtype.startsWith("-") && (0, NominalType_1.hasNominalType)(specified_subtypes, "-" + subtype)) {
879
- not_this_subtype = true;
880
- break;
881
- }
882
- const must_not_be_present = subtype.match(/^-(.*)$/);
883
- if (must_not_be_present && (0, NominalType_1.hasNominalType)(specified_subtypes, must_not_be_present[1])) {
884
- not_this_subtype = true;
885
- break;
886
- }
887
- }
888
- }
889
- if (!not_this_subtype) {
890
- let base;
891
- if (typeof (ending) != "string") {
892
- const lemma_ending = ending[0];
893
- const stem2_ending = ending[1];
894
- base = (0, common_1.extract_base)(lemma, lemma_ending);
895
- if (base && base + stem2_ending != stem2) {
896
- base = undefined;
897
- }
898
- }
899
- else {
900
- base = (0, common_1.extract_base)(lemma, ending);
901
- }
902
- if (base !== undefined) {
903
- const new_subtypes = [];
904
- for (const subtype of subtypes) {
905
- if (!subtype.startsWith("-")) {
906
- new_subtypes.push(subtype);
907
- }
908
- }
909
- if (process_retval) {
910
- [base, stem2] = process_retval(base, stem2);
911
- }
912
- if (process_stem2) {
913
- stem2 = stem2 || process_stem2(base);
914
- }
915
- return [base, stem2, rettype, new_subtypes];
916
- }
917
- }
918
- }
919
- if (decltype === undefined) {
920
- return ["", "", "", []];
921
- }
922
- else if (decltype == "") {
923
- throw Error(`Unrecognized ending for adjective: ${lemma}`);
924
- }
925
- else {
926
- throw Error(`Unrecognized ending for declension-${decltype} adjective: ${lemma}`);
927
- }
928
- }
929
- detect_noun_subtype(lemma, stem2, typ, subtypes) {
930
- if (typ == "1") {
931
- return this.get_noun_subtype_by_ending(lemma, stem2, typ, subtypes, [
932
- ["ām", [NominalType_1.NominalType.Feminine, NominalType_1.NominalType.Am]],
933
- ["ās", [NominalType_1.NominalType.Masculine, NominalType_1.NominalType.Greek, NominalType_1.NominalType.Ma]],
934
- ["ēs", [NominalType_1.NominalType.Masculine, NominalType_1.NominalType.Greek, NominalType_1.NominalType.Me]],
935
- ["ē", [NominalType_1.NominalType.Feminine, NominalType_1.NominalType.Greek]],
936
- ["ae", [NominalType_1.NominalType.Feminine, NominalType_1.NominalType.Plural]],
937
- ["a", [NominalType_1.NominalType.Feminine]],
938
- ]);
939
- }
940
- else if (typ == "2") {
941
- let detected_subtypes;
942
- [lemma, stem2, detected_subtypes] = this.get_noun_subtype_by_ending(lemma, stem2, typ, subtypes, [
943
- ["^(.*r)$", [NominalType_1.NominalType.Masculine, NominalType_1.NominalType.Er]],
944
- ["^(.*v)os$", [NominalType_1.NominalType.Masculine, NominalType_1.NominalType.Vos]],
945
- ["^(.*v)om$", [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Vom]],
946
- ["os", [NominalType_1.NominalType.Masculine, NominalType_1.NominalType.Greek]],
947
- ["os", [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Greek, NominalType_1.NominalType.Us]],
948
- ["on", [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Greek]],
949
- ["^([A-ZĀĒĪŌŪȲĂĔĬŎŬ].*)ius$", [NominalType_1.NominalType.Masculine, NominalType_1.NominalType.Ius, NominalType_1.NominalType.VocI, NominalType_1.NominalType.Singular]],
950
- [NominalType_1.NominalType.Ius, [NominalType_1.NominalType.Masculine, NominalType_1.NominalType.Ius]],
951
- [NominalType_1.NominalType.Ium, [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Ium]],
952
- [NominalType_1.NominalType.Us, [NominalType_1.NominalType.Masculine]],
953
- [NominalType_1.NominalType.Us, [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Us]],
954
- ["um", [NominalType_1.NominalType.Neuter]],
955
- ["iī", [NominalType_1.NominalType.Masculine, NominalType_1.NominalType.Ius, NominalType_1.NominalType.Plural]],
956
- ["ia", [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Ium, NominalType_1.NominalType.Plural]],
957
- ["ī", [NominalType_1.NominalType.Masculine, NominalType_1.NominalType.Plural]],
958
- ["ī", [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Us, NominalType_1.NominalType.Plural]],
959
- ["a", [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Plural]],
960
- ]);
961
- stem2 = stem2 || lemma;
962
- return [lemma, stem2, detected_subtypes];
963
- }
964
- else if (typ == "3") {
965
- let match;
966
- if (subtypes.has(NominalType_1.NominalType.Plural)) {
967
- if (subtypes.has(NominalType_1.NominalType.Greek)) {
968
- match = lemma.match(/^(.*)erēs$/);
969
- if (match) {
970
- return [match[1] + "ēr", match[1] + "er", new Set([NominalType_1.NominalType.Er])];
971
- }
972
- match = lemma.match(/^(.*)ontēs$/);
973
- if (match) {
974
- return [match[1] + "ōn", match[1] + "ont", new Set([NominalType_1.NominalType.On])];
975
- }
976
- match = lemma.match(/^(.*)es$/);
977
- if (match) {
978
- return ["foo", stem2 || match[1], new Set()];
979
- }
980
- throw Error(`Unrecognized ending for declension-3 plural Greek noun: ${lemma}`);
981
- }
982
- match = lemma.match(/^(.*)ia$/);
983
- if (match) {
984
- return ["foo", stem2 || match[1], new Set([NominalType_1.NominalType.Neuter, NominalType_1.NominalType.I, NominalType_1.NominalType.Pure])];
985
- }
986
- match = lemma.match(/^(.*)a$/);
987
- if (match) {
988
- return ["foo", stem2 || match[1], new Set([NominalType_1.NominalType.Neuter])];
989
- }
990
- match = lemma.match(/^(.*)ēs$/);
991
- if (match) {
992
- return ["foo", stem2 || match[1], new Set()];
993
- }
994
- throw Error(`Unrecognized ending for declension-3 plural noun: ${lemma}`);
995
- }
996
- stem2 = stem2 || LaNominal.make_stem2(lemma);
997
- let detected_subtypes;
998
- let base;
999
- let tmp;
1000
- if (subtypes.has(NominalType_1.NominalType.Greek)) {
1001
- [base, tmp, detected_subtypes] = this.get_noun_subtype_by_ending(lemma, stem2, "", subtypes, [
1002
- [["is", ""], [NominalType_1.NominalType.I]],
1003
- ["ēr", [NominalType_1.NominalType.Er]],
1004
- ["ōn", [NominalType_1.NominalType.On]],
1005
- ]);
1006
- if (base) {
1007
- return [lemma, stem2, detected_subtypes];
1008
- }
1009
- return [lemma, stem2, new Set()];
1010
- }
1011
- if (!subtypes.has(NominalType_1.NominalType.Neuter)) {
1012
- [base, tmp, detected_subtypes] = this.get_noun_subtype_by_ending(lemma, stem2, "", subtypes, [
1013
- [["^([A-ZĀĒĪŌŪȲĂĔĬŎŬ].*pol)is$", ""], [NominalType_1.NominalType.Feminine, NominalType_1.NominalType.Polis, NominalType_1.NominalType.Singular, NominalType_1.NominalType.Locative]],
1014
- [["tūdō", "tūdin"], [NominalType_1.NominalType.Feminine]],
1015
- [["tās", "tāt"], [NominalType_1.NominalType.Feminine]],
1016
- [["tūs", "tūt"], [NominalType_1.NominalType.Feminine]],
1017
- [["tiō", "tiōn"], [NominalType_1.NominalType.Feminine]],
1018
- [["siō", "siōn"], [NominalType_1.NominalType.Feminine]],
1019
- [["xiō", "xiōn"], [NominalType_1.NominalType.Feminine]],
1020
- [["gō", "gin"], [NominalType_1.NominalType.Feminine]],
1021
- [["or", "ōr"], [NominalType_1.NominalType.Masculine]],
1022
- [["trīx", "trīc"], [NominalType_1.NominalType.Feminine]],
1023
- [["trix", "trīc"], [NominalType_1.NominalType.Feminine]],
1024
- [["is", ""], [NominalType_1.NominalType.I]],
1025
- [["^([a-zāēīōūȳăĕĭŏŭ].*)ēs$", ""], [NominalType_1.NominalType.I]],
1026
- ]);
1027
- if (base) {
1028
- return [lemma, stem2, detected_subtypes];
1029
- }
1030
- }
1031
- [base, tmp, detected_subtypes] = this.get_noun_subtype_by_ending(lemma, stem2, "", subtypes, [
1032
- [["us", "or"], [NominalType_1.NominalType.Neuter]],
1033
- [["us", "er"], [NominalType_1.NominalType.Neuter]],
1034
- [["ma", "mat"], [NominalType_1.NominalType.Neuter]],
1035
- [["men", "min"], [NominalType_1.NominalType.Neuter]],
1036
- [["^([A-ZĀĒĪŌŪȲĂĔĬŎŬ].*)e$", ""], [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Singular]],
1037
- [["e", ""], [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.I, NominalType_1.NominalType.Pure]],
1038
- [["al", "āl"], [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.I, NominalType_1.NominalType.Pure]],
1039
- [["ar", "ār"], [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.I, NominalType_1.NominalType.Pure]],
1040
- ]);
1041
- if (base) {
1042
- return [lemma, stem2, detected_subtypes];
1043
- }
1044
- return [lemma, stem2, new Set()];
1045
- }
1046
- else if (typ == "4") {
1047
- if (subtypes.has(NominalType_1.NominalType.Echo) || subtypes.has(NominalType_1.NominalType.argo) || subtypes.has(NominalType_1.NominalType.Callisto)) {
1048
- const match = lemma.match(/^(.*)ō$/);
1049
- if (!match) {
1050
- throw Error(`Declension-4 noun of subtype .echo, .argo or .Callisto should end in -ō: ${lemma}`);
1051
- }
1052
- const base = match[1];
1053
- if (subtypes.has(NominalType_1.NominalType.Callisto)) {
1054
- return [base, "", new Set([NominalType_1.NominalType.Feminine, NominalType_1.NominalType.Singular])];
1055
- }
1056
- else {
1057
- return [base, "", new Set([NominalType_1.NominalType.Feminine])];
1058
- }
1059
- }
1060
- return this.get_noun_subtype_by_ending(lemma, stem2, typ, subtypes, [
1061
- ["us", [NominalType_1.NominalType.Masculine]],
1062
- ["ū", [NominalType_1.NominalType.Neuter]],
1063
- ["ūs", [NominalType_1.NominalType.Masculine, NominalType_1.NominalType.Plural]],
1064
- ["ua", [NominalType_1.NominalType.Neuter, NominalType_1.NominalType.Plural]],
1065
- ]);
1066
- }
1067
- else if (typ == "5") {
1068
- return this.get_noun_subtype_by_ending(lemma, stem2, typ, subtypes, [
1069
- ["iēs", [NominalType_1.NominalType.Feminine, NominalType_1.NominalType.i]],
1070
- ["iēs", [NominalType_1.NominalType.Feminine, NominalType_1.NominalType.i, NominalType_1.NominalType.Plural]],
1071
- ["ēs", [NominalType_1.NominalType.Feminine]],
1072
- ["ēs", [NominalType_1.NominalType.Feminine, NominalType_1.NominalType.Plural]],
1073
- ]);
1074
- }
1075
- else if (typ == "irreg" && lemma == "domus") {
1076
- return [lemma, "", new Set([NominalType_1.NominalType.Locative])];
1077
- }
1078
- else if (typ == "indecl" || (typ == "irreg" && (lemma == "Deus" || lemma == "Iēsus" || lemma == "Jēsus" || lemma == "Athōs" || lemma == "vēnum"))) {
1079
- return [lemma, "", new Set([NominalType_1.NominalType.Singular])];
1080
- }
1081
- else {
1082
- return [lemma, "", new Set()];
1083
- }
1084
- }
1085
- static make_stem2(stem) {
1086
- const patterns = [
1087
- ["tūdō", "tūdin"],
1088
- ["is", ""],
1089
- ["ēs", ""],
1090
- ["āns", "ant"],
1091
- ["ēns", "ent"],
1092
- ["ōns", "ont"],
1093
- ["ceps", "cipit"],
1094
- ["us", "or"],
1095
- ["ex", "ic"],
1096
- ["ma", "mat"],
1097
- ["e", ""],
1098
- ["al", "āl"],
1099
- ["ar", "ār"],
1100
- ["men", "min"],
1101
- ["er", "r"],
1102
- ["or", "ōr"],
1103
- ["", "gin"],
1104
- ["ō", "ōn"],
1105
- ["ps", "p"],
1106
- ["bs", "b"],
1107
- ["s", "t"],
1108
- ["x", "c"],
1109
- ];
1110
- for (const pattern of patterns) {
1111
- const key = pattern[0];
1112
- const val = pattern[1];
1113
- if (stem.match(new RegExp(key + "$"))) {
1114
- return stem.replace(new RegExp(key + "$"), val);
1115
- }
1116
- }
1117
- return stem;
1118
- }
1119
- get_noun_subtype_by_ending(lemma, stem2, decltype, specified_subtypes, endings_and_subtypes) {
1120
- for (const ending_and_subtype of endings_and_subtypes) {
1121
- const ending = ending_and_subtype[0];
1122
- const subtypes = ending_and_subtype[1];
1123
- let not_this_subtype = false;
1124
- if (specified_subtypes.has(NominalType_1.NominalType.Plural) && !subtypes.includes(NominalType_1.NominalType.Plural)) {
1125
- not_this_subtype = true;
1126
- }
1127
- else {
1128
- for (const subtype of subtypes) {
1129
- if ((0, NominalType_1.hasNominalType)(specified_subtypes, "-" + subtype) ||
1130
- (subtype == NominalType_1.NominalType.Neuter && (specified_subtypes.has(NominalType_1.NominalType.Masculine) || specified_subtypes.has(NominalType_1.NominalType.Feminine))) ||
1131
- ((subtype == NominalType_1.NominalType.Masculine || subtype == NominalType_1.NominalType.Feminine) && specified_subtypes.has(NominalType_1.NominalType.Neuter)) ||
1132
- (subtype == NominalType_1.NominalType.Singular && specified_subtypes.has(NominalType_1.NominalType.Plural)) ||
1133
- (subtype == NominalType_1.NominalType.Plural && specified_subtypes.has(NominalType_1.NominalType.Singular))) {
1134
- not_this_subtype = true;
1135
- break;
1136
- }
1137
- }
1138
- }
1139
- if (!not_this_subtype) {
1140
- if (Array.isArray(ending)) {
1141
- const lemma_ending = ending[0];
1142
- const stem2_ending = ending[1];
1143
- const base = (0, common_1.extract_base)(lemma, lemma_ending);
1144
- if (base && (base + stem2_ending) == stem2) {
1145
- return [base, stem2, new Set(subtypes)];
1146
- }
1147
- }
1148
- else {
1149
- const base = (0, common_1.extract_base)(lemma, ending);
1150
- if (base) {
1151
- return [base, stem2, new Set(subtypes)];
1152
- }
1153
- }
1154
- }
1155
- }
1156
- if (decltype) {
1157
- throw Error(`Unrecognized ending for declension-${decltype} noun: ${lemma}`);
1158
- }
1159
- return ["", "", new Set()];
1160
- }
1161
- decline_segment_run(parsed_run, pos, is_adj) {
1162
- const declensions = {
1163
- forms: new Map(),
1164
- notes: new Map(),
1165
- title: [],
1166
- subtitleses: [],
1167
- orig_titles: [],
1168
- categories: [],
1169
- voc: true,
1170
- noneut: false,
1171
- nomf: false,
1172
- };
1173
- for (const slot of this.iter_slots(is_adj)) {
1174
- (0, NominalForm_1.setNominalForm)(declensions.forms, slot, [""]);
1175
- }
1176
- for (const seg of parsed_run.segments) {
1177
- if (seg.type == "Segment" && seg.decl) {
1178
- seg.loc = parsed_run.loc;
1179
- seg.num = seg.num || parsed_run.num;
1180
- seg.gender = seg.gender || parsed_run.gender;
1181
- let data;
1182
- let potential_lemma_slots;
1183
- if (seg.is_adj) {
1184
- const decline = LaAdjData_1.m_adj_decl.get(seg.decl);
1185
- if (!decline) {
1186
- throw Error(`Unrecognized declension '${seg.decl}'`);
1187
- }
1188
- potential_lemma_slots = this.potential_adj_lemma_slots;
1189
- data = {
1190
- declOpts: this.options,
1191
- subtitles: [],
1192
- footnote: "",
1193
- num: seg.num,
1194
- gender: seg.gender,
1195
- loc: seg.loc,
1196
- voc: true,
1197
- noneut: false,
1198
- nomf: false,
1199
- pos: is_adj ? pos : "adjectives",
1200
- forms: new Map(),
1201
- types: seg.types,
1202
- categories: [],
1203
- notes: new Map(),
1204
- };
1205
- decline(data, seg.args);
1206
- if (!data.voc) {
1207
- declensions.voc = false;
1208
- }
1209
- if (data.noneut) {
1210
- declensions.noneut = true;
1211
- }
1212
- if (data.nomf) {
1213
- declensions.nomf = true;
1214
- }
1215
- if (data.types.has(NominalType_1.NominalType.SuffixN)) {
1216
- data.subtitles.push(["with", " 'm' optionally → 'n' in compounds"]);
1217
- }
1218
- else if (data.types.has(NominalType_1.NominalType.NotSuffixN)) {
1219
- data.subtitles.push(["without", " 'm' optionally → 'n' in compounds"]);
1220
- }
1221
- if (data.title) {
1222
- declensions.orig_titles.push(data.title);
1223
- }
1224
- if (data.subtitles.length > 0) {
1225
- const subtitles = [];
1226
- for (const subtitle of data.subtitles) {
1227
- if (typeof (subtitle) == "string") {
1228
- subtitles.push(subtitle);
1229
- }
1230
- else {
1231
- subtitles.push(subtitle.join(""));
1232
- }
1233
- }
1234
- data.title = data.title + " (" + subtitles.join(", ") + ")";
1235
- }
1236
- for (const subtitle of data.subtitles) {
1237
- declensions.subtitleses.push(subtitle);
1238
- }
1239
- }
1240
- else {
1241
- const decline = LaNounData_1.m_noun_decl.get(seg.decl);
1242
- if (!decline) {
1243
- throw Error(`Unrecognized declension '${seg.decl}'`);
1244
- }
1245
- potential_lemma_slots = this.potential_noun_lemma_slots;
1246
- data = {
1247
- declOpts: this.options,
1248
- subtitles: [],
1249
- footnote: "",
1250
- num: seg.num,
1251
- loc: seg.loc,
1252
- pos: pos,
1253
- forms: new Map(),
1254
- types: seg.types,
1255
- categories: [],
1256
- notes: new Map(),
1257
- };
1258
- decline(data, seg.args);
1259
- if (!data.title) {
1260
- const match = seg.headword_decl.match(/^irreg\/(.*)$/);
1261
- let apparent_decl;
1262
- if (match) {
1263
- apparent_decl = match[1];
1264
- if (data.subtitles.length == 0) {
1265
- data.subtitles.push("irregular");
1266
- }
1267
- }
1268
- else {
1269
- apparent_decl = seg.headword_decl;
1270
- }
1271
- const english = this.declension_to_english.get(apparent_decl);
1272
- if (english) {
1273
- data.title = `${english}-declension`;
1274
- }
1275
- else if (apparent_decl == "irreg") {
1276
- data.title = "irregular";
1277
- }
1278
- else if (apparent_decl == "indecl" || apparent_decl == "0") {
1279
- data.title = "indeclinable";
1280
- }
1281
- else {
1282
- throw Error(`Internal error! Don't recognize noun declension ${apparent_decl}`);
1283
- }
1284
- data.title = data.title + " noun";
1285
- }
1286
- if (data.types.has(NominalType_1.NominalType.SuffixN)) {
1287
- data.subtitles.push(["with", " 'm' optionally → 'n' in compounds"]);
1288
- }
1289
- else if (data.types.has(NominalType_1.NominalType.NotSuffixN)) {
1290
- data.subtitles.push(["without", " 'm' optionally → 'n' in compounds"]);
1291
- }
1292
- declensions.orig_titles.push(data.title);
1293
- if (data.subtitles.length > 0) {
1294
- const subtitles = [];
1295
- for (const subtitle of data.subtitles) {
1296
- if (typeof (subtitle) == "string") {
1297
- subtitles.push(subtitle);
1298
- }
1299
- else {
1300
- subtitles.push(subtitle.join(""));
1301
- }
1302
- }
1303
- data.title = data.title + ` (${subtitles.join(", ")})`;
1304
- }
1305
- for (const subtitle of data.subtitles) {
1306
- declensions.subtitleses.push(subtitle);
1307
- }
1308
- }
1309
- for (const slot of potential_lemma_slots) {
1310
- const forms = (0, NominalForm_1.getNominalForm)(data.forms, slot);
1311
- if (forms) {
1312
- const linked_forms = [];
1313
- for (const form of forms) {
1314
- if (form == seg.lemma) {
1315
- linked_forms.push(seg.orig_lemma);
1316
- }
1317
- else {
1318
- linked_forms.push(form);
1319
- }
1320
- }
1321
- (0, NominalForm_1.setNominalForm)(data.forms, `linked_${slot}`, linked_forms);
1322
- }
1323
- }
1324
- if (seg.types.has(NominalType_1.NominalType.Ligature)) {
1325
- this.apply_ligatures(data.forms, is_adj);
1326
- }
1327
- if (seg.types.has(NominalType_1.NominalType.SuffixN)) {
1328
- this.apply_sufn(data.forms, is_adj);
1329
- }
1330
- this.propagate_number_restrictions(data.forms, seg.num, is_adj);
1331
- for (const slot of this.iter_slots(is_adj)) {
1332
- let new_forms;
1333
- if (is_adj) {
1334
- if (!seg.is_adj) {
1335
- throw Error(`Can't decline noun '${seg.lemma}' when overall term is an adjective`);
1336
- }
1337
- new_forms = (0, NominalForm_1.getNominalForm)(data.forms, slot);
1338
- if (!new_forms && slot.match(/_[fn]$/)) {
1339
- new_forms = (0, NominalForm_1.getNominalForm)(data.forms, slot.replace(/_[fn]$/, "_m"));
1340
- }
1341
- }
1342
- else if (seg.is_adj) {
1343
- if (!seg.gender) {
1344
- throw Error(`Declining modifying adjective ${seg.lemma} but don't know gender of associated noun`);
1345
- }
1346
- new_forms = (0, NominalForm_1.getNominalForm)(data.forms, slot + "_" + seg.gender.toLowerCase()) ||
1347
- (0, NominalForm_1.getNominalForm)(data.forms, slot + "_m");
1348
- }
1349
- else {
1350
- new_forms = (0, NominalForm_1.getNominalForm)(data.forms, slot);
1351
- }
1352
- const new_notes = [];
1353
- if (new_forms) {
1354
- for (let j = 0; j < new_forms.length; j++) {
1355
- const noteses = data.notes.get(`${slot}${j + 1}`);
1356
- if (noteses) {
1357
- new_notes[j] = [noteses];
1358
- }
1359
- }
1360
- }
1361
- const oldForms = (0, NominalForm_1.getNominalForm)(declensions.forms, slot);
1362
- const [forms, notes] = this.append_form(oldForms, declensions.notes.get(slot), new_forms, new_notes, slot.includes("linked") ? seg.orig_prefix : seg.prefix);
1363
- (0, NominalForm_1.setNominalForm)(declensions.forms, slot, forms);
1364
- declensions.notes.set(slot, notes);
1365
- }
1366
- if (!seg.types.has(NominalType_1.NominalType.NoCategories) && (is_adj || !seg.is_adj)) {
1367
- for (const cat of data.categories) {
1368
- this.insert_if_not(declensions.categories, cat);
1369
- }
1370
- }
1371
- if (seg.prefix != "" && seg.prefix != "-" && seg.prefix != " ") {
1372
- declensions.title.push("indeclinable portion");
1373
- }
1374
- if (data.title) {
1375
- declensions.title.push(data.title);
1376
- }
1377
- }
1378
- else if (seg.type == "Alternant") {
1379
- let seg_declensions;
1380
- const seg_titles = [];
1381
- const seg_subtitleses = [];
1382
- const seg_stems_seen = [];
1383
- const seg_categories = [];
1384
- let title_the_hard_way = false;
1385
- let alternant_decl = "";
1386
- let alternant_decl_title;
1387
- for (const this_parsed_run of seg.alternants) {
1388
- let num_non_constant_segments = 0;
1389
- for (const segment of (this_parsed_run.segments)) {
1390
- if (segment.type == "Segment" && segment.decl) {
1391
- if (!alternant_decl) {
1392
- alternant_decl = segment.decl;
1393
- }
1394
- else if (alternant_decl != segment.decl) {
1395
- title_the_hard_way = true;
1396
- num_non_constant_segments = 500;
1397
- break;
1398
- }
1399
- num_non_constant_segments++;
1400
- }
1401
- }
1402
- if (num_non_constant_segments != 1) {
1403
- title_the_hard_way = true;
1404
- }
1405
- }
1406
- if (!title_the_hard_way) {
1407
- const subtypeses = new Set();
1408
- for (const this_parsed_run of seg.alternants) {
1409
- for (const segment of this_parsed_run.segments) {
1410
- if (segment.type == "Segment" && segment.decl) {
1411
- segment.types.forEach(t => subtypeses.add(t));
1412
- this.insert_if_not(seg_stems_seen, segment.stem2 || "");
1413
- }
1414
- }
1415
- }
1416
- for (const this_parsed_run of seg.alternants) {
1417
- for (const segment of this_parsed_run.segments) {
1418
- if (segment.type == "Segment" && segment.decl) {
1419
- const neg_subtypes = this.set_difference(subtypeses, segment.types);
1420
- for (const neg_subtype of neg_subtypes) {
1421
- (0, NominalType_1.addNominalType)(segment.types, "not_" + neg_subtype);
1422
- }
1423
- }
1424
- }
1425
- }
1426
- }
1427
- for (const this_parsed_run of seg.alternants) {
1428
- this_parsed_run.loc = seg.loc;
1429
- this_parsed_run.num = this_parsed_run.num || seg.num;
1430
- this_parsed_run.gender = this_parsed_run.gender || seg.gender;
1431
- const this_declensions = this.decline_segment_run(this_parsed_run, pos, is_adj);
1432
- if (!this_declensions.voc) {
1433
- declensions.voc = false;
1434
- }
1435
- if (this_declensions.noneut) {
1436
- declensions.noneut = true;
1437
- }
1438
- if (this_declensions.nomf) {
1439
- declensions.nomf = true;
1440
- }
1441
- if (this_parsed_run.num == "sg" || this_parsed_run.num == "pl") {
1442
- for (const slot of (this.iter_slots(is_adj))) {
1443
- if ((this_parsed_run.num == "sg" && slot.includes("pl")) ||
1444
- (this_parsed_run.num == "pl" && slot.includes("sg"))) {
1445
- (0, NominalForm_1.setNominalForm)(this_declensions.forms, slot, []);
1446
- this_declensions.notes.set(slot, []);
1447
- }
1448
- }
1449
- }
1450
- if (!seg_declensions) {
1451
- seg_declensions = this_declensions;
1452
- }
1453
- else {
1454
- for (const slot of this.iter_slots(is_adj)) {
1455
- const curforms = (0, NominalForm_1.getNominalForm)(seg_declensions.forms, slot) || [];
1456
- const newforms = (0, NominalForm_1.getNominalForm)(this_declensions.forms, slot) || [];
1457
- const newform_index_to_new_index = [];
1458
- newforms.forEach((form, newj) => {
1459
- let did_break = false;
1460
- for (let j = 0; j < curforms.length; j++) {
1461
- if (curforms[j] == form) {
1462
- newform_index_to_new_index[newj] = j;
1463
- did_break = true;
1464
- break;
1465
- }
1466
- }
1467
- if (!did_break) {
1468
- curforms.push(form);
1469
- newform_index_to_new_index[newj] = curforms.length - 1;
1470
- }
1471
- });
1472
- (0, NominalForm_1.setNominalForm)(seg_declensions.forms, slot, curforms);
1473
- const curnotes = seg_declensions.notes.get(slot) || [];
1474
- const newnotes = this_declensions.notes.get(slot);
1475
- if (newnotes) {
1476
- newnotes.forEach((notes, index) => {
1477
- const combined_index = newform_index_to_new_index[index];
1478
- if (!curnotes[combined_index]) {
1479
- curnotes[combined_index] = notes;
1480
- }
1481
- else {
1482
- const combined = Array.from(curnotes[combined_index]);
1483
- for (const note of newnotes) {
1484
- this.insert_if_not(combined, newnotes);
1485
- }
1486
- curnotes[combined_index] = combined;
1487
- }
1488
- });
1489
- }
1490
- }
1491
- }
1492
- for (const cat of this_declensions.categories) {
1493
- this.insert_if_not(seg_categories, cat);
1494
- }
1495
- this.insert_if_not(seg_titles, this_declensions.title.join(""));
1496
- seg_subtitleses.push(this_declensions.subtitleses);
1497
- if (!alternant_decl_title) {
1498
- alternant_decl_title = this_declensions.orig_titles[0];
1499
- }
1500
- }
1501
- if (!seg_declensions) {
1502
- throw Error("No segment declensions");
1503
- }
1504
- this.propagate_number_restrictions(seg_declensions === null || seg_declensions === void 0 ? void 0 : seg_declensions.forms, parsed_run.num, is_adj);
1505
- for (const slot of this.iter_slots(is_adj)) {
1506
- const declForms = (0, NominalForm_1.getNominalForm)(declensions.forms, slot);
1507
- const segForms = (0, NominalForm_1.getNominalForm)(seg_declensions.forms, slot);
1508
- const [newForms, notes] = this.append_form(declForms, declensions.notes.get(slot), segForms, seg_declensions.notes.get(slot), undefined);
1509
- (0, NominalForm_1.setNominalForm)(declensions.forms, slot, newForms);
1510
- declensions.notes.set(slot, notes);
1511
- }
1512
- if (is_adj || !seg.is_adj) {
1513
- for (const cat of seg_categories) {
1514
- this.insert_if_not(declensions.categories, cat);
1515
- }
1516
- }
1517
- let title_to_insert;
1518
- if (title_the_hard_way) {
1519
- title_to_insert = this.join_sentences(seg_titles, " or ");
1520
- }
1521
- else {
1522
- const first = seg_subtitleses[0];
1523
- if (typeof (first) == "string") {
1524
- throw Error("Expected multi-title");
1525
- }
1526
- const first_subtitles = first;
1527
- let num_common_subtitles = first_subtitles.length;
1528
- for (let i = 1; i < seg_subtitleses.length; i++) {
1529
- const this_subtitles = seg_subtitleses[i];
1530
- for (let j = 0; j < num_common_subtitles; j++) {
1531
- if (first_subtitles[j] != this_subtitles[j]) {
1532
- num_common_subtitles = j;
1533
- break;
1534
- }
1535
- }
1536
- }
1537
- const common_subtitles = [];
1538
- for (let i = 0; i < num_common_subtitles; i++) {
1539
- const entry = first_subtitles[i];
1540
- if (typeof (entry) != "string") {
1541
- common_subtitles.push(entry.join(""));
1542
- }
1543
- else {
1544
- common_subtitles.push(entry);
1545
- }
1546
- }
1547
- const common_subtitle_portion = common_subtitles.join(", ");
1548
- let non_common_subtitle_portion;
1549
- let common_prefix;
1550
- let common_suffix;
1551
- for (let i = 0; i < seg_subtitleses.length; i++) {
1552
- const this_subtitles = seg_subtitleses[i];
1553
- if (typeof (this_subtitles) == "string") {
1554
- throw Error("Expected subtitles to be array");
1555
- }
1556
- if (this_subtitles.length != num_common_subtitles + 1 || typeof (this_subtitles[num_common_subtitles]) == "string" || this_subtitles[num_common_subtitles].length != 2) {
1557
- break;
1558
- }
1559
- if (i == 0) {
1560
- common_prefix = this_subtitles[num_common_subtitles][0];
1561
- common_suffix = this_subtitles[num_common_subtitles][1];
1562
- }
1563
- else {
1564
- const this_prefix = this_subtitles[num_common_subtitles][0];
1565
- const this_suffix = this_subtitles[num_common_subtitles][1];
1566
- if (this_prefix != common_prefix) {
1567
- common_prefix = undefined;
1568
- }
1569
- if (this_suffix != common_suffix) {
1570
- common_suffix = undefined;
1571
- }
1572
- if (!common_prefix && !common_suffix) {
1573
- break;
1574
- }
1575
- }
1576
- }
1577
- if (common_prefix || common_suffix) {
1578
- if (common_prefix && common_suffix) {
1579
- throw Error("Something is wrong, first non-common subtitle is actually common to all segments");
1580
- }
1581
- if (common_prefix) {
1582
- const non_common_parts = [];
1583
- for (const subtitles of seg_subtitleses) {
1584
- non_common_parts.push(subtitles[num_common_subtitles][1]);
1585
- }
1586
- non_common_subtitle_portion = common_prefix + non_common_parts.join(" or ");
1587
- }
1588
- else {
1589
- const non_common_parts = [];
1590
- for (const subtitles of seg_subtitleses) {
1591
- non_common_parts.push(subtitles[num_common_subtitles][0]);
1592
- }
1593
- non_common_subtitle_portion = non_common_parts.join(" or ") + common_suffix;
1594
- }
1595
- }
1596
- else {
1597
- let saw_non_common_subtitles = false;
1598
- const non_common_subtitles = [];
1599
- for (const this_subtitles of seg_subtitleses) {
1600
- const this_non_common_subtitles = [];
1601
- for (let j = num_common_subtitles; j < this_subtitles.length; j++) {
1602
- this_non_common_subtitles.push(this_subtitles[j]);
1603
- }
1604
- if (this_non_common_subtitles.length > 0) {
1605
- non_common_subtitles.push(this_non_common_subtitles.join(", "));
1606
- saw_non_common_subtitles = true;
1607
- }
1608
- else {
1609
- non_common_subtitles.push("otherwise");
1610
- }
1611
- }
1612
- non_common_subtitle_portion = saw_non_common_subtitles ? non_common_subtitles.join(" or ") : "";
1613
- }
1614
- const subtitle_portions = [];
1615
- if (common_subtitle_portion) {
1616
- subtitle_portions.push(common_subtitle_portion);
1617
- }
1618
- if (non_common_subtitle_portion) {
1619
- subtitle_portions.push(non_common_subtitle_portion);
1620
- }
1621
- if (seg_stems_seen.length > 1) {
1622
- const number_to_english = [
1623
- "zero", "one", "two", "three", "four", "five"
1624
- ];
1625
- subtitle_portions.push((number_to_english[seg_stems_seen.length] || `${seg_stems_seen.length}`) + " different stems");
1626
- }
1627
- const subtitle_portion = subtitle_portions.join("; ");
1628
- if (subtitle_portion) {
1629
- title_to_insert = alternant_decl_title + " (" + subtitle_portion + ")";
1630
- }
1631
- else {
1632
- title_to_insert = alternant_decl_title;
1633
- }
1634
- }
1635
- if (title_to_insert) {
1636
- declensions.title.push(title_to_insert);
1637
- }
1638
- }
1639
- else {
1640
- for (const slot of this.iter_slots(is_adj)) {
1641
- const prefix = slot.includes("linked") ? seg.orig_prefix : seg.prefix;
1642
- const [forms, notes] = this.append_form((0, NominalForm_1.getNominalForm)(declensions.forms, slot), declensions.notes.get(slot), [prefix || ""], undefined, undefined);
1643
- (0, NominalForm_1.setNominalForm)(declensions.forms, slot, forms);
1644
- declensions.notes.set(slot, notes);
1645
- }
1646
- declensions.title.push("indeclinable portion");
1647
- }
1648
- }
1649
- const titles = [];
1650
- declensions.title.forEach((title, i) => {
1651
- if (i == 0) {
1652
- titles.push(title[0].toUpperCase() + title.substr(1));
1653
- }
1654
- else {
1655
- titles.push(this.add_indefinite_article(title));
1656
- }
1657
- });
1658
- declensions.title = [titles.join(" with ")];
1659
- return declensions;
1660
- }
1661
- add_indefinite_article(text) {
1662
- if (text.match(/^[aeiou]/i)) {
1663
- return `an ${text}`;
1664
- }
1665
- else {
1666
- return `a ${text}`;
1667
- }
1668
- }
1669
- join_sentences(sentences, joiner) {
1670
- const sentences_to_join = [];
1671
- sentences.forEach((sentence, i) => {
1672
- if (i < sentences.length - 1) {
1673
- sentence = sentence.replace(/\.$/, "");
1674
- }
1675
- if (i > 0) {
1676
- sentence = sentence[0].toLowerCase() + sentence.substr(1);
1677
- }
1678
- sentences_to_join.push(sentence);
1679
- });
1680
- return sentences_to_join.join(joiner);
1681
- }
1682
- set_difference(a, b) {
1683
- const res = new Set();
1684
- for (const key of a.keys()) {
1685
- if (!b.has(key)) {
1686
- res.add(key);
1687
- }
1688
- }
1689
- return res;
1690
- }
1691
- append_form(forms, notes, new_forms, new_notes, prefix) {
1692
- forms = forms || [];
1693
- new_forms = new_forms || [];
1694
- notes = notes || [];
1695
- new_notes = new_notes || [];
1696
- prefix = prefix || "";
1697
- if (new_forms.length == 1) {
1698
- for (let i = 0; i < forms.length; i++) {
1699
- forms[i] = forms[i] + prefix + new_forms[0];
1700
- if (new_notes[0]) {
1701
- if (!notes[i]) {
1702
- notes[i] = new_notes[0];
1703
- }
1704
- else {
1705
- const combined_notes = Array.from(notes[i]);
1706
- for (const note of new_notes[0]) {
1707
- combined_notes.push(note);
1708
- }
1709
- notes[i] = combined_notes;
1710
- }
1711
- }
1712
- }
1713
- return [forms, notes];
1714
- }
1715
- else {
1716
- const ret_forms = [];
1717
- const ret_notes = [];
1718
- for (let i = 0; i < forms.length; i++) {
1719
- for (let j = 0; j < new_forms.length; j++) {
1720
- ret_forms.push(forms[i] + prefix + new_forms[j]);
1721
- if (new_notes[j]) {
1722
- if (!notes[i]) {
1723
- ret_notes[i * new_forms.length + j] = new_notes[j];
1724
- }
1725
- else {
1726
- const combined_notes = Array.from(notes[i]);
1727
- for (const note of new_notes[j]) {
1728
- combined_notes.push(note);
1729
- }
1730
- ret_notes[i * new_forms.length + j] = combined_notes;
1731
- }
1732
- }
1733
- }
1734
- }
1735
- return [ret_forms, ret_notes];
1736
- }
1737
- }
1738
- apply_ligatures(forms, is_adj) {
1739
- for (const slot of this.iter_slots(is_adj)) {
1740
- const ffs = (0, NominalForm_1.getNominalForm)(forms, slot) || [];
1741
- for (let i = 0; i < ffs.length; i++) {
1742
- ffs[i] = ffs[i].replace(/Ae/g, "Æ");
1743
- ffs[i] = ffs[i].replace(/Oe/g, "Œ");
1744
- ffs[i] = ffs[i].replace(/ae/g, "æ");
1745
- ffs[i] = ffs[i].replace(/oe/g, "œ");
1746
- }
1747
- (0, NominalForm_1.setNominalForm)(forms, slot, ffs);
1748
- }
1749
- }
1750
- apply_sufn(forms, is_adj) {
1751
- for (const slot of this.iter_slots(is_adj)) {
1752
- const ffs = (0, NominalForm_1.getNominalForm)(forms, slot) || [];
1753
- if (ffs.length == 1 && !slot.includes("linked")) {
1754
- const form = ffs[0];
1755
- if (form.match(/m$/)) {
1756
- (0, NominalForm_1.setNominalForm)(forms, slot, [form.replace(/m$/, "n"), ...ffs]);
1757
- }
1758
- }
1759
- else {
1760
- let final_m = false;
1761
- for (const form of (0, NominalForm_1.getNominalForm)(forms, slot) || []) {
1762
- if (form.match(/m$/)) {
1763
- final_m = true;
1764
- }
1765
- }
1766
- if (final_m) {
1767
- const newval = [];
1768
- for (const form of (0, NominalForm_1.getNominalForm)(forms, slot) || []) {
1769
- if (form.match(/m$/)) {
1770
- const val = form.replace(/m$/, "n");
1771
- newval.push(val);
1772
- }
1773
- newval.push(form);
1774
- }
1775
- (0, NominalForm_1.setNominalForm)(forms, slot, newval);
1776
- }
1777
- }
1778
- }
1779
- }
1780
- propagate_number_restrictions(forms, num, is_adj) {
1781
- if (num == "sg" || num == "pl") {
1782
- for (const slot of this.iter_slots(is_adj)) {
1783
- if (slot.match(num)) {
1784
- const other_num_slot = (num == "sg") ? slot.replace("sg", "pl") : slot.replace("pl", "sg");
1785
- (0, NominalForm_1.setNominalForm)(forms, other_num_slot, (0, NominalForm_1.getNominalForm)(forms, slot) || []);
1786
- }
1787
- }
1788
- }
1789
- }
1790
- insert_if_not(data, entry, pos = 0) {
1791
- if (data.includes(entry)) {
1792
- return;
1793
- }
1794
- if (pos == 0) {
1795
- data.push(entry);
1796
- }
1797
- else {
1798
- data.splice(pos - 1, 0, entry);
1799
- }
1800
- }
1801
- }
1802
- exports.LaNominal = LaNominal;
1803
- LaNominal.EmptyForm = "—";
1804
- //# sourceMappingURL=LaNominal.js.map
1
+ /**
2
+ * This is a complete re-implementation of Wiktionary's Module:la-nominal, developed by Benwing2.
3
+ * It was converted from Lua to TypeScript by Folke Will <folko@solhost.org>.
4
+ *
5
+ * Original source: https://en.wiktionary.org/wiki/Module:la-nominal
6
+ * Based on version 2026-03-03
7
+ *
8
+ * Lua idioms, function and variable names kept as in the original in order to easily
9
+ * backport later changes to this implementation.
10
+ *
11
+ * For that reason, it's suggested to add a type-aware wrapper around this class and leave
12
+ * this code unchanged instead of improving the types and use of idioms in this class.
13
+ *
14
+ */
15
+ import { array_equals, extract_base, is_enum_value, read_list, remove_links } from "../common.js";
16
+ import { m_adj_decl } from "./LaAdjData.js";
17
+ import { m_noun_decl } from "./LaNounData.js";
18
+ import { getNominalForm, setNominalForm } from "./NominalForm.js";
19
+ import { addNominalType, delNominalType, hasNominalType, NominalType } from "./NominalType.js";
20
+ export var Gender;
21
+ (function (Gender) {
22
+ Gender["M"] = "M";
23
+ Gender["F"] = "F";
24
+ Gender["N"] = "N";
25
+ })(Gender || (Gender = {}));
26
+ export var NumberTantum;
27
+ (function (NumberTantum) {
28
+ NumberTantum["Singular"] = "sg";
29
+ NumberTantum["Plural"] = "pl";
30
+ NumberTantum["Both"] = "both";
31
+ })(NumberTantum || (NumberTantum = {}));
32
+ export class LaNominal {
33
+ static EmptyForm = "";
34
+ options;
35
+ cases = ["nom", "gen", "acc", "dat", "abl", "voc", "loc"];
36
+ genders = ["m", "f", "n"];
37
+ nums = ["sg", "pl"];
38
+ linked_prefixes = ["", "linked_"];
39
+ potential_noun_lemma_slots = [
40
+ "nom_sg", "gen_sg", "acc_sg",
41
+ "nom_pl", "gen_pl", "acc_pl",
42
+ ];
43
+ potential_adj_lemma_slots = [
44
+ "nom_sg_m", "gen_sg_m", "acc_sg_m",
45
+ "nom_pl_m", "gen_pl_m", "acc_pl_m",
46
+ "nom_sg_f", "gen_sg_f", "acc_sg_f",
47
+ "nom_pl_f", "gen_pl_f", "acc_pl_f",
48
+ "nom_sg_n", "gen_sg_n", "acc_sg_n",
49
+ "nom_pl_n", "gen_pl_n", "acc_pl_n",
50
+ ];
51
+ irreg_noun_to_decl = new Map([
52
+ ["bōs", "3"],
53
+ ["cherub", "irreg"],
54
+ ["deus", "2"],
55
+ ["Deus", "2"],
56
+ ["domus", "4,2"],
57
+ ["iūgerum", "2,3"],
58
+ ["jūgerum", "2,3"],
59
+ ["sūs", "3"],
60
+ ["ēthos", "3"],
61
+ ["Athōs", "2"],
62
+ ["lexis", "3"],
63
+ ["vēnum", "4,2"],
64
+ ["vīs", "3"],
65
+ ]);
66
+ declension_to_english = new Map([
67
+ ["1", "first"],
68
+ ["2", "second"],
69
+ ["3", "third"],
70
+ ["4", "fourth"],
71
+ ["5", "fifth"],
72
+ ]);
73
+ title;
74
+ constructor(options) {
75
+ this.options = options || {};
76
+ }
77
+ do_generate_noun_forms(args, pos = "nouns", from_headword = false, title) {
78
+ this.title = title;
79
+ const parsed_run = this.parse_segment_run_allowing_alternants(args.get("1")?.trim() || "");
80
+ parsed_run.loc = parsed_run.loc || args.has("loc_sg") || args.has("loc_pl");
81
+ let num = args.get("num");
82
+ if (num !== undefined && !is_enum_value(NumberTantum, num)) {
83
+ num = undefined;
84
+ }
85
+ parsed_run.num = num || parsed_run.num;
86
+ const declensions = this.decline_segment_run(parsed_run, pos, false);
87
+ if (!parsed_run.loc) {
88
+ setNominalForm(declensions.forms, "loc_sg", undefined);
89
+ setNominalForm(declensions.forms, "loc_pl", undefined);
90
+ }
91
+ declensions.title = [this.construct_title(args.get("title"), declensions.title.join(""), false, parsed_run)];
92
+ const all_data = {
93
+ templateType: "declension",
94
+ declensionType: "noun",
95
+ title: declensions.title.join(" "),
96
+ num: parsed_run.num,
97
+ gender: parsed_run.gender,
98
+ propses: parsed_run.propses,
99
+ forms: declensions.forms,
100
+ categories: declensions.categories,
101
+ notes: new Map(),
102
+ user_specified: new Set(),
103
+ pos: pos,
104
+ num_type: args.get("type"),
105
+ // only if headword
106
+ indecl: args.has("indecl"),
107
+ m: read_list(args, "m"),
108
+ f: read_list(args, "f"),
109
+ overriding_lemma: read_list(args, "lemma"),
110
+ overriding_genders: read_list(args, "g")
111
+ };
112
+ for (const slot of this.iter_noun_slots()) {
113
+ const noteses = declensions.notes.get(slot);
114
+ if (noteses) {
115
+ noteses.forEach((notes, index) => {
116
+ all_data.notes.set(`${slot}${index + 1}`, notes);
117
+ });
118
+ }
119
+ }
120
+ this.unattested_forms(args, false);
121
+ this.process_noun_forms_and_overrides(all_data, args);
122
+ return all_data;
123
+ }
124
+ do_generate_adj_forms(args, pos = "adjectives", from_headword = false, title) {
125
+ this.title = title;
126
+ let segment_run = args.get("1")?.trim() || "";
127
+ if (!segment_run.match(/[<(]/)) {
128
+ segment_run = segment_run + (args.has("indecl") ? "<0+>" : "<+>");
129
+ }
130
+ const parsed_run = this.parse_segment_run_allowing_alternants(segment_run);
131
+ parsed_run.loc = parsed_run.loc || (args.has("loc_sg_m") || args.has("loc_sg_f") || args.has("loc_sg_n") || args.has("loc_pl_m") || args.has("loc_pl_f") || args.has("loc_pl_n"));
132
+ let num = args.get("num");
133
+ if (num !== undefined && !is_enum_value(NumberTantum, num)) {
134
+ num = undefined;
135
+ }
136
+ parsed_run.num = num || parsed_run.num;
137
+ const overriding_voc = (args.has("voc_sg_m") || args.has("voc_sg_f") || args.has("voc_sg_n") || args.has("voc_pl_m") || args.has("voc_pl_f") || args.has("voc_pl_n"));
138
+ const declensions = this.decline_segment_run(parsed_run, pos, true);
139
+ if (!parsed_run.loc) {
140
+ setNominalForm(declensions.forms, "loc_sg_m", undefined);
141
+ setNominalForm(declensions.forms, "loc_sg_f", undefined);
142
+ setNominalForm(declensions.forms, "loc_sg_n", undefined);
143
+ setNominalForm(declensions.forms, "loc_pl_m", undefined);
144
+ setNominalForm(declensions.forms, "loc_pl_f", undefined);
145
+ setNominalForm(declensions.forms, "loc_pl_n", undefined);
146
+ }
147
+ if (!overriding_voc && !declensions.voc) {
148
+ setNominalForm(declensions.forms, "voc_sg_m", undefined);
149
+ setNominalForm(declensions.forms, "voc_sg_f", undefined);
150
+ setNominalForm(declensions.forms, "voc_sg_n", undefined);
151
+ setNominalForm(declensions.forms, "voc_pl_m", undefined);
152
+ setNominalForm(declensions.forms, "voc_pl_f", undefined);
153
+ setNominalForm(declensions.forms, "voc_pl_n", undefined);
154
+ }
155
+ declensions.title = [this.construct_title(args.get("title"), declensions.title.join(""), from_headword, parsed_run)];
156
+ const all_data = {
157
+ templateType: "declension",
158
+ declensionType: "adjective",
159
+ title: declensions.title.join(""),
160
+ num: parsed_run.num,
161
+ propses: parsed_run.propses,
162
+ forms: declensions.forms,
163
+ categories: declensions.categories,
164
+ notes: new Map(),
165
+ user_specified: new Set(),
166
+ voc: declensions.voc,
167
+ noneut: args.has("noneut") || declensions.noneut,
168
+ nomf: args.has("nomf") || declensions.nomf,
169
+ pos: pos,
170
+ num_type: args.get("type"),
171
+ // only if headword
172
+ overriding_lemma: read_list(args, "lemma"),
173
+ indecl: args.has("indecl"),
174
+ comp: read_list(args, "comp"),
175
+ sup: read_list(args, "sup"),
176
+ adv: read_list(args, "adv")
177
+ };
178
+ for (const slot of this.iter_adj_slots()) {
179
+ const noteses = declensions.notes.get(slot);
180
+ if (noteses) {
181
+ noteses.forEach((notes, index) => {
182
+ all_data.notes.set(`${slot}${index + 1}`, notes);
183
+ });
184
+ }
185
+ }
186
+ // Expand genderless slots (e.g. gen_pl → gen_pl_m/f/n) to match Lua behaviour
187
+ this.unattested_forms(args, true);
188
+ for (const slot of this.iter_noun_slots(true)) {
189
+ const arg = args.get(slot);
190
+ if (arg !== undefined) {
191
+ for (const gender of this.genders) {
192
+ const slotG = slot + "_" + gender;
193
+ if (!args.has(slotG)) {
194
+ args.set(slotG, arg);
195
+ }
196
+ }
197
+ args.delete(slot);
198
+ }
199
+ }
200
+ this.process_adj_forms_and_overrides(all_data, args);
201
+ return all_data;
202
+ }
203
+ construct_title(args_title, declensions_title, from_headword, parsed_run) {
204
+ if (args_title) {
205
+ declensions_title = args_title.replace("<1>", "first declension");
206
+ declensions_title = declensions_title.replace("<1&2>", "first/second declension");
207
+ declensions_title = declensions_title.replace("<2>", "second declension");
208
+ declensions_title = declensions_title.replace("<3>", "third declension");
209
+ declensions_title = declensions_title.replace("<4>", "fourth declension");
210
+ declensions_title = declensions_title.replace("<5>", "fifth declension");
211
+ if (from_headword) {
212
+ declensions_title = declensions_title[0].toLowerCase() + declensions_title.replace(/\.$/, "").slice(1);
213
+ }
214
+ else {
215
+ if (declensions_title.startsWith(" ")) {
216
+ declensions_title = declensions_title.slice(1);
217
+ }
218
+ declensions_title = this.uppercase_first_visible(declensions_title);
219
+ }
220
+ }
221
+ else {
222
+ const post_text_parts = [];
223
+ if (parsed_run.loc) {
224
+ post_text_parts.push(", with locative");
225
+ }
226
+ if (parsed_run.num == "sg") {
227
+ post_text_parts.push(", singular only");
228
+ }
229
+ else if (parsed_run.num == "pl") {
230
+ post_text_parts.push(", plural only");
231
+ }
232
+ const post_text = post_text_parts.join("");
233
+ if (from_headword) {
234
+ declensions_title = declensions_title[0].toLowerCase() + declensions_title.slice(1) + post_text;
235
+ }
236
+ else {
237
+ if (declensions_title.length > 0) {
238
+ declensions_title = this.uppercase_first_visible(declensions_title) + post_text + ".";
239
+ }
240
+ }
241
+ }
242
+ return declensions_title;
243
+ }
244
+ process_noun_forms_and_overrides(data, args) {
245
+ const linked_to_non_linked_noun_slots = new Map();
246
+ for (const slot of this.potential_noun_lemma_slots) {
247
+ linked_to_non_linked_noun_slots.set("linked_" + slot, slot);
248
+ }
249
+ for (const slot of this.iter_noun_slots()) {
250
+ let val = [];
251
+ const slotArg = args.get(slot);
252
+ if (slotArg) {
253
+ val = slotArg.split("/") || [];
254
+ data.user_specified.add(slot);
255
+ }
256
+ else {
257
+ const non_linked_equiv_slot = linked_to_non_linked_noun_slots.get(slot);
258
+ const nonLinkedArg = non_linked_equiv_slot ? args.get(non_linked_equiv_slot) : undefined;
259
+ if (nonLinkedArg) {
260
+ val = nonLinkedArg.split("/") || [];
261
+ data.user_specified.add(slot);
262
+ }
263
+ else {
264
+ val = getNominalForm(data.forms, slot) || [];
265
+ }
266
+ }
267
+ if (val) {
268
+ if ((data.num == "pl" && slot.includes("sg")) || (data.num == "sg" && slot.includes("pl"))) {
269
+ setNominalForm(data.forms, slot, [""]);
270
+ }
271
+ else if (val[0] == "" || val[0] == "-" || val[0] == "—") {
272
+ if (val[1] !== undefined) {
273
+ throw Error(`Cannot specify additional forms for ${slot} if it has been cancelled with "-"`);
274
+ }
275
+ setNominalForm(data.forms, slot, [LaNominal.EmptyForm]);
276
+ }
277
+ else {
278
+ setNominalForm(data.forms, slot, val);
279
+ }
280
+ }
281
+ }
282
+ }
283
+ unattested_forms(args, is_adj) {
284
+ for (const slot of this.iter_slots(is_adj)) {
285
+ const arg = args.get(slot);
286
+ if (arg !== undefined) {
287
+ const match = arg.match(/^\*(.*)$/);
288
+ if (match) {
289
+ const stripped = match[1];
290
+ if (stripped) {
291
+ args.set(slot, stripped);
292
+ }
293
+ else {
294
+ args.delete(slot);
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+ process_adj_forms_and_overrides(data, args) {
301
+ const linked_to_non_linked_adj_slots = new Map();
302
+ for (const slot of this.potential_adj_lemma_slots) {
303
+ linked_to_non_linked_adj_slots.set("linked_" + slot, slot);
304
+ }
305
+ for (const slot of this.iter_adj_slots()) {
306
+ if (data.noneut && slot.match(/_n$/)) {
307
+ setNominalForm(data.forms, slot, undefined);
308
+ }
309
+ if (data.nomf && (slot.match(/_m/) || slot.match(/_f/))) {
310
+ setNominalForm(data.forms, slot, undefined);
311
+ }
312
+ let val;
313
+ const ovr = args.get(slot);
314
+ if (ovr) {
315
+ val = ovr.split("/");
316
+ data.user_specified.add(slot);
317
+ }
318
+ else {
319
+ const non_linked_equiv_slot = linked_to_non_linked_adj_slots.get(slot);
320
+ const nonLinkedArg = non_linked_equiv_slot ? args.get(non_linked_equiv_slot) : undefined;
321
+ if (nonLinkedArg) {
322
+ val = nonLinkedArg.split("/") || [];
323
+ data.user_specified.add(slot);
324
+ }
325
+ else {
326
+ val = getNominalForm(data.forms, slot);
327
+ }
328
+ }
329
+ if (val) {
330
+ if ((data.num == "pl" && slot.match(/sg/)) || (data.num == "sg" && slot.match(/pl/))) {
331
+ setNominalForm(data.forms, slot, undefined);
332
+ }
333
+ else if (val[0] == "" || val[0] == "-" || val[0] == "—") {
334
+ if (val[1] !== undefined) {
335
+ throw Error(`Cannot specify additional forms for ${slot} if it has been cancelled with "-"`);
336
+ }
337
+ setNominalForm(data.forms, slot, [LaNominal.EmptyForm]);
338
+ }
339
+ else {
340
+ setNominalForm(data.forms, slot, val);
341
+ }
342
+ }
343
+ }
344
+ for (const gender of ["f", "n"]) {
345
+ let other_is_masc = true;
346
+ for (const cas of this.cases) {
347
+ for (const num of this.nums) {
348
+ const genderForm = getNominalForm(data.forms, cas + "_" + num + "_" + gender);
349
+ const amscForm = getNominalForm(data.forms, cas + "_" + num + "_m");
350
+ if (!array_equals(genderForm, amscForm)) {
351
+ other_is_masc = false;
352
+ break;
353
+ }
354
+ }
355
+ if (!other_is_masc) {
356
+ break;
357
+ }
358
+ }
359
+ if (other_is_masc && !this.options.populateAllTerminations) {
360
+ for (const cas of this.cases) {
361
+ for (const num of this.nums) {
362
+ setNominalForm(data.forms, cas + "_" + num + "_" + gender, undefined);
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }
368
+ iter_slots(is_adj, overridable_only = false) {
369
+ if (is_adj) {
370
+ return this.iter_adj_slots(overridable_only);
371
+ }
372
+ else {
373
+ return this.iter_noun_slots(overridable_only);
374
+ }
375
+ }
376
+ iter_adj_slots(overridable_only = false) {
377
+ let cas = 1;
378
+ let num = 1;
379
+ let gen = 1;
380
+ let linked_variant = 0;
381
+ const entries = [];
382
+ while (true) {
383
+ linked_variant = linked_variant + 1;
384
+ const max_linked_variant = (overridable_only || cas > 3) ? 1 : 2;
385
+ if (linked_variant > max_linked_variant) {
386
+ linked_variant = 1;
387
+ gen++;
388
+ if (gen > this.genders.length) {
389
+ gen = 1;
390
+ num++;
391
+ if (num > this.nums.length) {
392
+ num = 1;
393
+ cas++;
394
+ if (cas > this.cases.length) {
395
+ break;
396
+ }
397
+ }
398
+ }
399
+ }
400
+ entries.push(this.linked_prefixes[linked_variant - 1] + this.cases[cas - 1] + "_" + this.nums[num - 1] + "_" + this.genders[gen - 1]);
401
+ }
402
+ return entries;
403
+ }
404
+ iter_noun_slots(overridable_only = false) {
405
+ let cas = 1;
406
+ let num = 1;
407
+ let linked_variant = 0;
408
+ const entries = [];
409
+ while (true) {
410
+ linked_variant = linked_variant + 1;
411
+ const max_linked_variant = (overridable_only || cas > 3) ? 1 : 2;
412
+ if (linked_variant > max_linked_variant) {
413
+ linked_variant = 1;
414
+ num++;
415
+ if (num > this.nums.length) {
416
+ num = 1;
417
+ cas++;
418
+ if (cas > this.cases.length) {
419
+ break;
420
+ }
421
+ }
422
+ }
423
+ entries.push(this.linked_prefixes[linked_variant - 1] + this.cases[cas - 1] + "_" + this.nums[num - 1]);
424
+ }
425
+ return entries;
426
+ }
427
+ parse_segment_run_allowing_alternants(segment_run) {
428
+ const alternating_segments = this.capturing_split(segment_run, /(\(\(.*?\)\))/);
429
+ const parsed_segments = [];
430
+ let loc = false;
431
+ let num;
432
+ let gender;
433
+ let is_adj;
434
+ const propses = [];
435
+ for (let i = 0; i < alternating_segments.length; i++) {
436
+ const alternating_segment = alternating_segments[i];
437
+ let this_is_adj;
438
+ if (alternating_segment) {
439
+ if (i % 2 == 0) {
440
+ const parsed_run = this.parse_segment_run(alternating_segment);
441
+ for (const parsed_segment of parsed_run.segments) {
442
+ parsed_segments.push(parsed_segment);
443
+ }
444
+ loc = loc || parsed_run.loc;
445
+ num = num || parsed_run.num;
446
+ gender = gender || parsed_run.gender;
447
+ this_is_adj = parsed_run.is_adj;
448
+ for (const prop of parsed_run.propses) {
449
+ propses.push(prop);
450
+ }
451
+ }
452
+ else {
453
+ const parsed_alternating_segment = this.parse_alternant(alternating_segment);
454
+ parsed_segments.push(parsed_alternating_segment);
455
+ loc = loc || parsed_alternating_segment.loc;
456
+ num = num || parsed_alternating_segment.num;
457
+ gender = gender || parsed_alternating_segment.gender;
458
+ this_is_adj = parsed_alternating_segment.is_adj;
459
+ propses.push(...parsed_alternating_segment.propses);
460
+ }
461
+ }
462
+ if (is_adj === undefined) {
463
+ is_adj = this_is_adj;
464
+ }
465
+ else if (this_is_adj !== undefined) {
466
+ is_adj = is_adj && this_is_adj;
467
+ }
468
+ }
469
+ return {
470
+ segments: parsed_segments,
471
+ loc: loc,
472
+ num: num,
473
+ gender: gender,
474
+ is_adj: is_adj,
475
+ propses: propses,
476
+ };
477
+ }
478
+ parse_alternant(alternant) {
479
+ const parsed_alternants = [];
480
+ const alternant_spec = alternant.match(/^\(\((.*)\)\)$/);
481
+ let loc = false;
482
+ let num;
483
+ let gender;
484
+ let is_adj;
485
+ const propses = [];
486
+ if (!alternant_spec) {
487
+ throw Error("Invalid alternant spec");
488
+ }
489
+ const alternants = alternant_spec[1].split(",");
490
+ alternants.forEach((altr, i) => {
491
+ const parsed_run = this.parse_segment_run(altr);
492
+ parsed_alternants.push(parsed_run);
493
+ loc = loc || parsed_run.loc;
494
+ if (i == 0) {
495
+ num = parsed_run.num;
496
+ }
497
+ else if (num != parsed_run.num) {
498
+ num = NumberTantum.Both;
499
+ }
500
+ gender = gender || parsed_run.gender;
501
+ if (is_adj === undefined) {
502
+ is_adj = parsed_run.is_adj;
503
+ }
504
+ else if (parsed_run.is_adj !== undefined && parsed_run.is_adj !== is_adj) {
505
+ throw Error("Saw both noun and adjective alternants; not allowed");
506
+ }
507
+ propses.push(...parsed_run.propses);
508
+ });
509
+ return {
510
+ type: "Alternant",
511
+ alternants: parsed_alternants,
512
+ loc: loc,
513
+ num: num,
514
+ gender: gender,
515
+ is_adj: is_adj,
516
+ propses: propses,
517
+ };
518
+ }
519
+ parse_segment_run(segment_run) {
520
+ const is_suffix = segment_run.startsWith("-");
521
+ const segments = [];
522
+ const bracketed_segments = this.capturing_split(segment_run, /(\[\[[^\[\]]-\]\]<.*?>)/);
523
+ bracketed_segments.forEach((bracketed_segment, i) => {
524
+ if (i % 2 == 1) {
525
+ segments.push(bracketed_segment);
526
+ }
527
+ else {
528
+ let regex;
529
+ if (is_suffix) {
530
+ regex = /([^<> ,]+<.*?>)/;
531
+ }
532
+ else {
533
+ regex = /([^<> ,\-]+<.*?>)/;
534
+ }
535
+ for (const subsegment of this.capturing_split(bracketed_segment, regex)) {
536
+ segments.push(subsegment);
537
+ }
538
+ }
539
+ });
540
+ let loc = false;
541
+ let num;
542
+ let gender;
543
+ let is_adj;
544
+ const propses = [];
545
+ const parsed_segments = [];
546
+ for (let i = 1; i < segments.length; i += 2) {
547
+ const parsed_segment = this.parse_segment(segments[i]);
548
+ loc = loc || parsed_segment.loc;
549
+ num = num || parsed_segment.num;
550
+ if (is_adj === undefined) {
551
+ is_adj = parsed_segment.is_adj;
552
+ }
553
+ else {
554
+ is_adj = is_adj && parsed_segment.is_adj;
555
+ }
556
+ gender = gender || parsed_segment.gender;
557
+ parsed_segment.orig_prefix = segments[i - 1];
558
+ parsed_segment.prefix = remove_links(segments[i - 1]);
559
+ parsed_segments.push(parsed_segment);
560
+ if (parsed_segment.props) {
561
+ propses.push(parsed_segment.props);
562
+ }
563
+ }
564
+ if (segments[segments.length - 1]) {
565
+ parsed_segments.push({
566
+ type: "Segment",
567
+ args: [],
568
+ decl: "",
569
+ is_adj: false,
570
+ lemma: "",
571
+ orig_lemma: "",
572
+ loc: false,
573
+ types: new Set(),
574
+ orig_prefix: segments[segments.length - 1],
575
+ prefix: remove_links(segments[segments.length - 1])
576
+ });
577
+ }
578
+ return {
579
+ segments: parsed_segments,
580
+ loc: loc,
581
+ num: num,
582
+ gender: gender,
583
+ propses: propses,
584
+ };
585
+ }
586
+ capturing_split(str, pattern) {
587
+ return str.split(pattern);
588
+ }
589
+ parse_segment(segment) {
590
+ const match = segment.match(/^(.*)<(.*?)>$/);
591
+ if (!match) {
592
+ throw Error("No match");
593
+ }
594
+ const stem_part = match[1];
595
+ const spec_part = match[2];
596
+ const stems = stem_part.split("/");
597
+ const specs = spec_part.split(".");
598
+ const types = new Set();
599
+ let num;
600
+ let loc = false;
601
+ let decl = "";
602
+ for (let j = 0; j < specs.length; j++) {
603
+ let spec = specs[j];
604
+ if (j == 0) {
605
+ decl = spec;
606
+ }
607
+ else {
608
+ const m2 = spec.match(/^(-?)(.*?)$/);
609
+ if (m2) {
610
+ const begins_with_hypen = m2[1];
611
+ spec = m2[2];
612
+ spec = begins_with_hypen + spec.replace(/-/g, "_");
613
+ if (is_enum_value(NominalType, spec)) {
614
+ addNominalType(types, spec);
615
+ }
616
+ }
617
+ }
618
+ }
619
+ const orig_lemma = stems[0] || this.title;
620
+ if (!orig_lemma) {
621
+ throw Error("No lemma");
622
+ }
623
+ const lemma = remove_links(orig_lemma);
624
+ let stem2 = stems[1];
625
+ if (stems.length > 2) {
626
+ throw Error(`Too many stems, at most 2 should be give: ${stem_part}`);
627
+ }
628
+ let is_adj = false;
629
+ let base;
630
+ let detected_subtypes;
631
+ if (decl.match(/\+/)) {
632
+ [base, stem2, decl, detected_subtypes] = this.detect_adj_type_and_subtype(lemma, stem2, decl, types);
633
+ is_adj = true;
634
+ for (const subtype of detected_subtypes) {
635
+ if (hasNominalType(types, "-" + subtype)) {
636
+ delNominalType(types, "-" + subtype);
637
+ }
638
+ else {
639
+ types.add(subtype);
640
+ }
641
+ }
642
+ }
643
+ else {
644
+ [base, stem2, detected_subtypes] = this.detect_noun_subtype(lemma, stem2, decl, types);
645
+ for (const subtype of detected_subtypes) {
646
+ if (hasNominalType(types, "-" + subtype)) {
647
+ delNominalType(types, "-" + subtype);
648
+ }
649
+ else if ((subtype == NominalType.Masculine || subtype == NominalType.Feminine || subtype == NominalType.Neuter) &&
650
+ (types.has(NominalType.Masculine) || types.has(NominalType.Feminine) || types.has(NominalType.Neuter))) {
651
+ // don't create conflicting gender specs
652
+ }
653
+ else if ((subtype == NominalType.Singular || subtype == NominalType.Plural || subtype == NominalType.Both) &&
654
+ (types.has(NominalType.Singular) || types.has(NominalType.Plural) || types.has(NominalType.Both) || types.has(NominalType.SingularFull) || types.has(NominalType.PluralFull))) {
655
+ // don't create conflicting number restrictions
656
+ }
657
+ else {
658
+ types.add(subtype);
659
+ }
660
+ }
661
+ if (!types.has(NominalType.Plural) && !types.has(NominalType.PluralFull) && !types.has(NominalType.Both) && !types.has(NominalType.SingularFull) && lemma.match(/^[A-ZĀĒĪŌŪȲĂĔĬŎŬÆŒ]/)) {
662
+ types.add(NominalType.Singular);
663
+ }
664
+ }
665
+ if (types.has(NominalType.Locative)) {
666
+ loc = true;
667
+ types.delete(NominalType.Locative);
668
+ }
669
+ let gender;
670
+ if (types.has(NominalType.Masculine)) {
671
+ gender = Gender.M;
672
+ }
673
+ else if (types.has(NominalType.Feminine)) {
674
+ gender = Gender.F;
675
+ }
676
+ else if (types.has(NominalType.Neuter)) {
677
+ gender = Gender.N;
678
+ }
679
+ if (types.has(NominalType.Plural)) {
680
+ num = NumberTantum.Plural;
681
+ types.delete(NominalType.Plural);
682
+ }
683
+ else if (types.has(NominalType.PluralFull)) {
684
+ num = NumberTantum.Plural;
685
+ // keep PluralFull in types (Lua keeps "plural" in types)
686
+ }
687
+ else if (types.has(NominalType.Singular)) {
688
+ num = NumberTantum.Singular;
689
+ types.delete(NominalType.Singular);
690
+ }
691
+ else if (types.has(NominalType.SingularFull)) {
692
+ num = NumberTantum.Singular;
693
+ // keep SingularFull in types (Lua keeps "singular" in types)
694
+ }
695
+ const args = [base, stem2];
696
+ const props = {
697
+ decl: decl,
698
+ types: types,
699
+ };
700
+ return {
701
+ type: "Segment",
702
+ decl: decl,
703
+ is_adj: is_adj,
704
+ gender: gender,
705
+ orig_lemma: orig_lemma,
706
+ lemma: lemma,
707
+ stem2: stem2,
708
+ types: types,
709
+ num: num,
710
+ loc: loc,
711
+ args: args,
712
+ props: props,
713
+ };
714
+ }
715
+ detect_adj_type_and_subtype(lemma, stem2, typ, subtypes) {
716
+ if (!typ.match(/^[0123]/) && !typ.match(/^irreg/) && typ && typ != "+" && typ != "0+" && typ != "indecl+") {
717
+ subtypes = new Set(subtypes);
718
+ addNominalType(subtypes, typ.replace(/\+$/, ""));
719
+ typ = "+";
720
+ }
721
+ function base_as_stem2(base, stem2x) {
722
+ return [base, base];
723
+ }
724
+ function constant_base(baseval) {
725
+ return (base, s2) => [baseval, ""];
726
+ }
727
+ function decl12_stem2(base) {
728
+ return base;
729
+ }
730
+ function decl3_stem2(base) {
731
+ return LaNominal.make_stem2(base);
732
+ }
733
+ const decl12_entries = [
734
+ ["us", "1&2+", []],
735
+ ["a", "1&2+", []],
736
+ ["um", "1&2+", []],
737
+ ["ī", "1&2+", [NominalType.Plural]],
738
+ ["ae", "1&2+", [NominalType.Plural]],
739
+ ["a", "1&2+", [NominalType.Plural]],
740
+ ["os", "1&2+", [NominalType.GreekA, NominalType.NoGreekE]],
741
+ ["os", "1&2+", [NominalType.GreekE, NominalType.NoGreekA]],
742
+ ["ē", "1&2+", [NominalType.GreekE, NominalType.NoGreekA]],
743
+ ["on", "1&2+", [NominalType.GreekA, NominalType.NoGreekE]],
744
+ ["on", "1&2+", [NominalType.GreekE, NominalType.NoGreekA]],
745
+ ["^(.*er)$", "1&2+", [NominalType.Er]],
746
+ ["^(.*ur)$", "1&2+", [NominalType.Er]],
747
+ ["^(h)ic$", "1&2+", [NominalType.Ic]],
748
+ ];
749
+ const decl3_entries = [
750
+ ["^(.*er)$", "3-3+", [NominalType.I]],
751
+ ["^(.*er)$", "3-3+", []],
752
+ ["is", "3-2+", [NominalType.I]],
753
+ ["is", "3-2+", []],
754
+ ["e", "3-2+", [NominalType.I]],
755
+ ["e", "3-2+", []],
756
+ ["^(.*[ij])or$", "3-C+", [NominalType.AblEI, NominalType.NoAblIE]],
757
+ ["^(.*[ij])or$", "3-C+", []],
758
+ ["^(min)or$", "3-C+", [NominalType.AblEI, NominalType.NoAblIE]],
759
+ ["^(min)or$", "3-C+", []],
760
+ ["^(.*trīx)$", "3-def+", [NominalType.Trix, NominalType.AblIE, NominalType.NoAblEI]],
761
+ ["^(.*trīx)$", "3-def+", [NominalType.Trix]],
762
+ ["^(.*tor)$", "3-def+", [NominalType.Tor, NominalType.AblIE, NominalType.NoAblEI]],
763
+ ["^(.*tor)$", "3-def+", [NominalType.Tor]],
764
+ ["^(.*ēs)$", "3-1+", [NominalType.I, NominalType.AblIE, NominalType.NoAblEI]],
765
+ ["^(.*ēs)$", "3-1+", [NominalType.I]],
766
+ ["^(.*ēs)$", "3-1+", [NominalType.Par, NominalType.AblIE, NominalType.NoAblEI]],
767
+ ["^(.*ēs)$", "3-1+", [NominalType.Par]],
768
+ ["^(.*[ij])ōrēs$", "3-C+", [NominalType.Plural]],
769
+ ["^(min)ōrēs$", "3-C+", [NominalType.Plural]],
770
+ ["ēs", "3-2+", [NominalType.Plural, NominalType.I], base_as_stem2],
771
+ ["ēs", "3-1+", [NominalType.Plural, NominalType.Par], base_as_stem2],
772
+ ["ia", "3-2+", [NominalType.Plural, NominalType.I], base_as_stem2],
773
+ ["a", "3-1+", [NominalType.Plural, NominalType.Par], base_as_stem2],
774
+ ["", "3-1+", [NominalType.I, NominalType.AblIE, NominalType.NoAblEI]],
775
+ ["", "3-1+", [NominalType.I]],
776
+ ["", "3-1+", [NominalType.Par, NominalType.AblIE, NominalType.NoAblEI]],
777
+ ["", "3-1+", [NominalType.Par]],
778
+ ];
779
+ if (!typ || typ == "+") {
780
+ const [base, new_stem2, rettype, new_subtypes] = this.get_adj_type_and_subtype_by_ending(lemma, stem2, undefined, subtypes, decl12_entries, decl12_stem2);
781
+ if (base) {
782
+ return [base, new_stem2, rettype, new_subtypes];
783
+ }
784
+ else {
785
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, decl3_entries, decl3_stem2);
786
+ }
787
+ }
788
+ else if (typ == "indecl+") {
789
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
790
+ ["", typ, [NominalType.Singular]],
791
+ ["", typ, [NominalType.Plural]],
792
+ ["", typ, [NominalType.Both]],
793
+ ], decl3_stem2);
794
+ }
795
+ else if (typ == "0+") {
796
+ return [lemma, "", "0+", []];
797
+ }
798
+ else if (typ == "1&2+") {
799
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, decl12_entries, decl12_stem2);
800
+ }
801
+ else if (typ == "3+") {
802
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, decl3_entries, decl3_stem2);
803
+ }
804
+ else if (typ == "1-1+") {
805
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
806
+ ["a", "1-1+", []],
807
+ ["ae", "1-1+", [NominalType.Plural]]
808
+ ]);
809
+ }
810
+ else if (typ == "2-2+") {
811
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
812
+ ["us", "2-2+", []],
813
+ ["um", "2-2+", []],
814
+ ["ī", "2-2+", [NominalType.Plural]],
815
+ ["a", "2-2+", [NominalType.Plural]],
816
+ ["os", "2-2+", [NominalType.greek]],
817
+ ["on", "2-2+", [NominalType.greek]],
818
+ ["oe", "2-2+", [NominalType.greek, NominalType.Plural]],
819
+ ]);
820
+ }
821
+ else if (typ == "3-1+") {
822
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
823
+ ["^(.*ēs)$", "3-1+", [NominalType.I, NominalType.AblIE, NominalType.NoAblEI]],
824
+ ["^(.*ēs)$", "3-1+", [NominalType.I]],
825
+ ["^(.*ēs)$", "3-1+", [NominalType.Par, NominalType.AblIE, NominalType.NoAblEI]],
826
+ ["^(.*ēs)$", "3-1+", [NominalType.Par]],
827
+ ["ēs", "3-1+", [NominalType.Plural, NominalType.I], base_as_stem2],
828
+ ["ēs", "3-1+", [NominalType.Plural, NominalType.Par], base_as_stem2],
829
+ ["ia", "3-1+", [NominalType.Plural, NominalType.I], base_as_stem2],
830
+ ["a", "3-1+", [NominalType.Plural, NominalType.Par], base_as_stem2],
831
+ ["", "3-1+", [NominalType.I, NominalType.AblIE, NominalType.NoAblEI]],
832
+ ["", "3-1+", [NominalType.I]],
833
+ ["", "3-1+", [NominalType.Par, NominalType.AblIE, NominalType.NoAblEI]],
834
+ ["", "3-1+", [NominalType.Par]],
835
+ ], decl3_stem2);
836
+ }
837
+ else if (typ == "3-2+") {
838
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
839
+ ["is", "3-2+", []],
840
+ ["e", "3-2+", []],
841
+ ["ēs", "3-2+", []],
842
+ ["ēs", "3-2+", [NominalType.Plural]],
843
+ ["ia", "3-2+", [NominalType.Plural]],
844
+ ], decl3_stem2);
845
+ }
846
+ else if (typ == "3-3+" || typ == "3-P+") {
847
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
848
+ ["ēs", typ, [NominalType.Plural], base_as_stem2],
849
+ ["ia", typ, [NominalType.Plural], base_as_stem2],
850
+ ["", typ, []],
851
+ ], decl3_stem2);
852
+ }
853
+ else if (typ == "3-C+") {
854
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
855
+ ["^(.*[ij])or$", "3-C+", [NominalType.AblEI, NominalType.NoAblIE]],
856
+ ["^(.*[ij])or$", "3-C+", []],
857
+ ["^(min)or$", "3-C+", [NominalType.AblEI, NominalType.NoAblIE]],
858
+ ["^(min)or$", "3-C+", []],
859
+ ["^(.*[ij])ōrēs$", "3-C+", [NominalType.Plural]],
860
+ ["^(min)ōrēs$", "3-C+", [NominalType.Plural]],
861
+ ], decl3_stem2);
862
+ }
863
+ else if (typ == "3-def+") {
864
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
865
+ ["^(.*trīx)$", "3-def+", [NominalType.Trix, NominalType.AblIE, NominalType.NoAblEI]],
866
+ ["^(.*trīx)$", "3-def+", [NominalType.Trix]],
867
+ ["^(.*tor)$", "3-def+", [NominalType.Tor, NominalType.AblIE, NominalType.NoAblEI]],
868
+ ["^(.*tor)$", "3-def+", [NominalType.Tor]],
869
+ ["ēs", "3-def+", [NominalType.Plural], base_as_stem2],
870
+ ["ia", "3-def+", [NominalType.Plural], base_as_stem2],
871
+ ["", "3-def+", []],
872
+ ], decl3_stem2);
873
+ }
874
+ else if (typ == "irreg+") {
875
+ return this.get_adj_type_and_subtype_by_ending(lemma, stem2, typ, subtypes, [
876
+ ["^(duo)$", typ, [NominalType.Plural]],
877
+ ["^(ambō)$", typ, [NominalType.Plural]],
878
+ ["^(mīll?ia)$", typ, [NominalType.Neuter, NominalType.Plural], constant_base("mīlle")],
879
+ ["^(ea)$", typ, [], constant_base("is")],
880
+ ["^(id)$", typ, [], constant_base("is")],
881
+ ["^([ei]ī)$", typ, [NominalType.Plural], constant_base("is")],
882
+ ["^(eae?)$", typ, [NominalType.Plural], constant_base("is")],
883
+ ["^(eadem)$", typ, [], constant_base("īdem")],
884
+ ["^([īi]dem)$", typ, [], constant_base("īdem")],
885
+ ["^(īdem)$", typ, [NominalType.Plural]],
886
+ ["^(eae?dem)$", typ, [NominalType.Plural], constant_base("īdem")],
887
+ ["^(i[lps][lst])a$", typ, [], (base, s2) => [base + "e", ""]],
888
+ ["^(i[ls][lt])ud$", typ, [], (base, s2) => [base + "e", ""]],
889
+ ["^(ipsum)$", typ, [], constant_base("ipse")],
890
+ ["^(i[lps][lst])ī$", typ, [NominalType.Plural], (base, s2) => [base + "e", ""]],
891
+ ["^(i[lps][lst])ae?$", typ, [NominalType.Plural], (base, s2) => [base + "e", ""]],
892
+ ["^(quī)$", typ, []],
893
+ ["^(quī)$", typ, [NominalType.Plural]],
894
+ ["^(quae)$", typ, [], constant_base("quī")],
895
+ ["^(quae)$", typ, [NominalType.Plural], constant_base("quī")],
896
+ ["^(quid)$", typ, [], constant_base("quis")],
897
+ ["^(quod)$", typ, [], constant_base("quī")],
898
+ ["^(qui[cd]quid)$", typ, [], constant_base("quisquis")],
899
+ ["^(quīquī)$", typ, [NominalType.Plural], constant_base("quisquis")],
900
+ ["^(quaequae)$", typ, [NominalType.Plural], constant_base("quisquis")],
901
+ ["", typ, []],
902
+ ]);
903
+ }
904
+ else {
905
+ return [lemma, "", typ, []];
906
+ }
907
+ }
908
+ get_adj_type_and_subtype_by_ending(lemma, stem2, decltype, specified_subtypes, endings_and_subtypes, process_stem2) {
909
+ for (const [ending, rettype, subtypes, process_retval] of endings_and_subtypes) {
910
+ let not_this_subtype = false;
911
+ if (specified_subtypes.has(NominalType.Plural) && !subtypes.includes(NominalType.Plural)) {
912
+ not_this_subtype = true;
913
+ }
914
+ else {
915
+ for (const subtype of subtypes) {
916
+ if (!subtype.startsWith("-") && hasNominalType(specified_subtypes, "-" + subtype)) {
917
+ not_this_subtype = true;
918
+ break;
919
+ }
920
+ const must_not_be_present = subtype.match(/^-(.*)$/);
921
+ if (must_not_be_present && hasNominalType(specified_subtypes, must_not_be_present[1])) {
922
+ not_this_subtype = true;
923
+ break;
924
+ }
925
+ }
926
+ }
927
+ if (!not_this_subtype) {
928
+ let base;
929
+ if (typeof (ending) != "string") {
930
+ const lemma_ending = ending[0];
931
+ const stem2_ending = ending[1];
932
+ base = extract_base(lemma, lemma_ending);
933
+ if (base && base + stem2_ending != stem2) {
934
+ base = undefined;
935
+ }
936
+ }
937
+ else {
938
+ base = extract_base(lemma, ending);
939
+ }
940
+ if (base !== undefined) {
941
+ const new_subtypes = [];
942
+ for (const subtype of subtypes) {
943
+ if (!subtype.startsWith("-")) {
944
+ new_subtypes.push(subtype);
945
+ }
946
+ }
947
+ if (process_retval) {
948
+ [base, stem2] = process_retval(base, stem2);
949
+ }
950
+ if (process_stem2) {
951
+ stem2 = stem2 || process_stem2(base);
952
+ }
953
+ return [base, stem2, rettype, new_subtypes];
954
+ }
955
+ }
956
+ }
957
+ if (decltype === undefined) {
958
+ return ["", "", "", []];
959
+ }
960
+ else if (decltype == "") {
961
+ throw Error(`Unrecognized ending for adjective: ${lemma}`);
962
+ }
963
+ else {
964
+ throw Error(`Unrecognized ending for declension-${decltype} adjective: ${lemma}`);
965
+ }
966
+ }
967
+ detect_noun_subtype(lemma, stem2, typ, subtypes) {
968
+ if (typ == "1") {
969
+ return this.get_noun_subtype_by_ending(lemma, stem2, typ, subtypes, [
970
+ ["ām", [NominalType.Feminine, NominalType.Am]],
971
+ ["ās", [NominalType.Masculine, NominalType.Greek, NominalType.Ma]],
972
+ ["ēs", [NominalType.Masculine, NominalType.Greek, NominalType.Me]],
973
+ ["ā", [NominalType.Feminine, NominalType.Greek, NominalType.Fa]],
974
+ ["ē", [NominalType.Feminine, NominalType.Greek]],
975
+ ["ae", [NominalType.Feminine, NominalType.Plural]],
976
+ ["a", [NominalType.Feminine]],
977
+ ["a", [NominalType.Neuter]],
978
+ ["a", [NominalType.Neuter, NominalType.Plural]],
979
+ ]);
980
+ }
981
+ else if (typ == "2") {
982
+ let detected_subtypes;
983
+ [lemma, stem2, detected_subtypes] = this.get_noun_subtype_by_ending(lemma, stem2, typ, subtypes, [
984
+ ["^(.*r)$", [NominalType.Masculine, NominalType.Er]],
985
+ ["^(.*v)os$", [NominalType.Masculine, NominalType.Vos]],
986
+ ["^(.*v)om$", [NominalType.Neuter, NominalType.Vom]],
987
+ ["os", [NominalType.Masculine, NominalType.Greek]],
988
+ ["os", [NominalType.Neuter, NominalType.Greek, NominalType.Us]],
989
+ ["on", [NominalType.Neuter, NominalType.Greek]],
990
+ ["^([A-ZĀĒĪŌŪȲĂĔĬŎŬÆŒ].*)ius$", [NominalType.Masculine, NominalType.Ius, NominalType.VocI, NominalType.Singular]],
991
+ [NominalType.Ius, [NominalType.Masculine, NominalType.Ius]],
992
+ [NominalType.Ium, [NominalType.Neuter, NominalType.Ium]],
993
+ [NominalType.Us, [NominalType.Masculine]],
994
+ [NominalType.Us, [NominalType.Neuter, NominalType.Us]],
995
+ ["um", [NominalType.Neuter]],
996
+ ["iī", [NominalType.Masculine, NominalType.Ius, NominalType.Plural]],
997
+ ["ia", [NominalType.Neuter, NominalType.Ium, NominalType.Plural]],
998
+ ["ī", [NominalType.Masculine, NominalType.Plural]],
999
+ ["ī", [NominalType.Neuter, NominalType.Us, NominalType.Plural]],
1000
+ ["oe", [NominalType.Masculine, NominalType.Greek, NominalType.Plural]],
1001
+ ["a", [NominalType.Neuter, NominalType.Plural]],
1002
+ ]);
1003
+ stem2 = stem2 || lemma;
1004
+ return [lemma, stem2, detected_subtypes];
1005
+ }
1006
+ else if (typ == "3") {
1007
+ let match;
1008
+ if (subtypes.has(NominalType.Plural)) {
1009
+ if (subtypes.has(NominalType.Greek)) {
1010
+ match = lemma.match(/^(.*)erēs$/);
1011
+ if (match) {
1012
+ return [match[1] + "ēr", match[1] + "er", [NominalType.Er]];
1013
+ }
1014
+ match = lemma.match(/^(.*)ontēs$/);
1015
+ if (match) {
1016
+ return [match[1] + "ōn", match[1] + "ont", [NominalType.On]];
1017
+ }
1018
+ match = lemma.match(/^(.*)es$/);
1019
+ if (match) {
1020
+ return [match[1], stem2 || match[1], []];
1021
+ }
1022
+ throw Error(`Unrecognized ending for declension-3 plural Greek noun: ${lemma}`);
1023
+ }
1024
+ match = lemma.match(/^(.*)ia$/);
1025
+ if (match) {
1026
+ return [match[1], stem2 || match[1], [NominalType.Neuter, NominalType.I, NominalType.Pure]];
1027
+ }
1028
+ match = lemma.match(/^(.*)a$/);
1029
+ if (match) {
1030
+ return [match[1], stem2 || match[1], [NominalType.Neuter]];
1031
+ }
1032
+ match = lemma.match(/^(.*)ēs$/);
1033
+ if (match) {
1034
+ return [match[1], stem2 || match[1], []];
1035
+ }
1036
+ throw Error(`Unrecognized ending for declension-3 plural noun: ${lemma}`);
1037
+ }
1038
+ stem2 = stem2 || LaNominal.make_stem2(lemma);
1039
+ let detected_subtypes;
1040
+ let base;
1041
+ let tmp;
1042
+ if (subtypes.has(NominalType.Greek)) {
1043
+ [base, tmp, detected_subtypes] = this.get_noun_subtype_by_ending(lemma, stem2, "", subtypes, [
1044
+ [["is", ""], [NominalType.I]],
1045
+ ["ēr", [NominalType.Er]],
1046
+ ["ōn", [NominalType.On]],
1047
+ ]);
1048
+ if (base) {
1049
+ return [lemma, stem2, detected_subtypes];
1050
+ }
1051
+ return [lemma, stem2, []];
1052
+ }
1053
+ if (!subtypes.has(NominalType.Neuter)) {
1054
+ [base, tmp, detected_subtypes] = this.get_noun_subtype_by_ending(lemma, stem2, "", subtypes, [
1055
+ [["^([A-ZĀĒĪŌŪȲĂĔĬŎŬ].*pol)is$", ""], [NominalType.Feminine, NominalType.Polis, NominalType.Singular, NominalType.Locative]],
1056
+ [["tūdō", "tūdin"], [NominalType.Feminine]],
1057
+ [["tās", "tāt"], [NominalType.Feminine]],
1058
+ [["tūs", "tūt"], [NominalType.Feminine]],
1059
+ [["tiō", "tiōn"], [NominalType.Feminine]],
1060
+ [["siō", "siōn"], [NominalType.Feminine]],
1061
+ [["xiō", "xiōn"], [NominalType.Feminine]],
1062
+ [["", "gin"], [NominalType.Feminine]],
1063
+ [["or", "ōr"], [NominalType.Masculine]],
1064
+ [["trīx", "trīc"], [NominalType.Feminine]],
1065
+ [["trix", "trīc"], [NominalType.Feminine]],
1066
+ [["is", ""], [NominalType.I]],
1067
+ [["^([a-zāēīōūȳăĕĭŏŭ].*)ēs$", ""], [NominalType.I]],
1068
+ ]);
1069
+ if (base) {
1070
+ return [lemma, stem2, detected_subtypes];
1071
+ }
1072
+ }
1073
+ [base, tmp, detected_subtypes] = this.get_noun_subtype_by_ending(lemma, stem2, "", subtypes, [
1074
+ [["us", "or"], [NominalType.Neuter]],
1075
+ [["us", "er"], [NominalType.Neuter]],
1076
+ [["ma", "mat"], [NominalType.Neuter]],
1077
+ [["men", "min"], [NominalType.Neuter]],
1078
+ [["^([A-ZĀĒĪŌŪȲĂĔĬŎŬ].*)e$", ""], [NominalType.Neuter, NominalType.Singular]],
1079
+ [["e", ""], [NominalType.Neuter, NominalType.I, NominalType.Pure]],
1080
+ [["al", "āl"], [NominalType.Neuter, NominalType.I, NominalType.Pure]],
1081
+ [["ar", "ār"], [NominalType.Neuter, NominalType.I, NominalType.Pure]],
1082
+ ]);
1083
+ if (base) {
1084
+ return [lemma, stem2, detected_subtypes];
1085
+ }
1086
+ return [lemma, stem2, []];
1087
+ }
1088
+ else if (typ == "4") {
1089
+ if (subtypes.has(NominalType.Echo) || subtypes.has(NominalType.Callisto)) {
1090
+ const match = lemma.match(/^(.*)ō$/);
1091
+ if (!match) {
1092
+ throw Error(`Declension-4 noun of subtype .echo or .Callisto should end in -ō: ${lemma}`);
1093
+ }
1094
+ const base = match[1];
1095
+ if (subtypes.has(NominalType.Callisto)) {
1096
+ return [base, "", [NominalType.Feminine, NominalType.Singular]];
1097
+ }
1098
+ else {
1099
+ return [base, "", [NominalType.Feminine]];
1100
+ }
1101
+ }
1102
+ return this.get_noun_subtype_by_ending(lemma, stem2, typ, subtypes, [
1103
+ ["us", [NominalType.Masculine]],
1104
+ ["ū\u0306?", [NominalType.Neuter]],
1105
+ ["ūs", [NominalType.Masculine, NominalType.Plural]],
1106
+ ["ua", [NominalType.Neuter, NominalType.Plural]],
1107
+ ]);
1108
+ }
1109
+ else if (typ == "5") {
1110
+ return this.get_noun_subtype_by_ending(lemma, stem2, typ, subtypes, [
1111
+ ["iēs", [NominalType.Feminine, NominalType.i]],
1112
+ ["iēs", [NominalType.Feminine, NominalType.i, NominalType.Plural]],
1113
+ ["ēs", [NominalType.Feminine]],
1114
+ ["ēs", [NominalType.Feminine, NominalType.Plural]],
1115
+ ]);
1116
+ }
1117
+ else if (typ == "irreg" && lemma == "domus") {
1118
+ return [lemma, "", [NominalType.Locative]];
1119
+ }
1120
+ else if (typ == "sgpl") {
1121
+ return [lemma, stem2, []];
1122
+ }
1123
+ else if (typ == "indecl" || (typ == "irreg" && (lemma == "Deus" || lemma == "Iēsus" || lemma == "Jēsus" || lemma == "Athōs" || lemma == "vēnum"))) {
1124
+ return [lemma, "", [NominalType.Singular]];
1125
+ }
1126
+ else {
1127
+ return [lemma, "", []];
1128
+ }
1129
+ }
1130
+ static make_stem2(stem) {
1131
+ const patterns = [
1132
+ ["tūdō", "tūdin"],
1133
+ ["is", ""],
1134
+ ["ēs", ""],
1135
+ ["āns", "ant"],
1136
+ ["ēns", "ent"],
1137
+ ["ōns", "ont"],
1138
+ // Lua: ([āēīōūȳĀĒĪŌŪȲ]n)s → strip macron + "t"
1139
+ ["[āīūȳĀĪŪȲ]ns", (m) => m.replace(/[āĀ]/g, "a").replace(/[īĪ]/g, "i").replace(/[ūŪ]/g, "u").replace(/[ȳȲ]/g, "y").replace(/s$/, "t")],
1140
+ ["ceps", "cipit"],
1141
+ ["us", "or"],
1142
+ ["ex", "ic"],
1143
+ ["ma", "mat"],
1144
+ ["e", ""],
1145
+ ["ol", "ōl"],
1146
+ ["el", "ell"],
1147
+ ["El", "Ell"],
1148
+ ["al", "āl"],
1149
+ ["ar", "ār"],
1150
+ ["men", "min"],
1151
+ ["er", "r"],
1152
+ ["or", "ōr"],
1153
+ ["gō", "gin"],
1154
+ ["ō", "ōn"],
1155
+ ["ps", "p"],
1156
+ ["bs", "b"],
1157
+ ["s", "t"],
1158
+ ["x", "c"],
1159
+ ];
1160
+ for (const [key, val] of patterns) {
1161
+ if (stem.match(new RegExp(key + "$"))) {
1162
+ if (typeof val === "function") {
1163
+ return stem.replace(new RegExp(key + "$"), val);
1164
+ }
1165
+ return stem.replace(new RegExp(key + "$"), val);
1166
+ }
1167
+ }
1168
+ return stem;
1169
+ }
1170
+ get_noun_subtype_by_ending(lemma, stem2, decltype, specified_subtypes, endings_and_subtypes) {
1171
+ for (const ending_and_subtype of endings_and_subtypes) {
1172
+ const ending = ending_and_subtype[0];
1173
+ const subtypes = ending_and_subtype[1];
1174
+ let not_this_subtype = false;
1175
+ if (specified_subtypes.has(NominalType.Plural) && !subtypes.includes(NominalType.Plural)) {
1176
+ not_this_subtype = true;
1177
+ }
1178
+ else {
1179
+ for (const subtype of subtypes) {
1180
+ if (hasNominalType(specified_subtypes, "-" + subtype) ||
1181
+ (subtype == NominalType.Neuter && (specified_subtypes.has(NominalType.Masculine) || specified_subtypes.has(NominalType.Feminine))) ||
1182
+ ((subtype == NominalType.Masculine || subtype == NominalType.Feminine) && specified_subtypes.has(NominalType.Neuter)) ||
1183
+ (subtype == NominalType.Singular && specified_subtypes.has(NominalType.Plural)) ||
1184
+ (subtype == NominalType.Plural && specified_subtypes.has(NominalType.Singular))) {
1185
+ not_this_subtype = true;
1186
+ break;
1187
+ }
1188
+ }
1189
+ }
1190
+ if (!not_this_subtype) {
1191
+ if (Array.isArray(ending)) {
1192
+ const lemma_ending = ending[0];
1193
+ const stem2_ending = ending[1];
1194
+ const base = extract_base(lemma, lemma_ending);
1195
+ if (base && (base + stem2_ending) == stem2) {
1196
+ return [base, stem2, subtypes];
1197
+ }
1198
+ }
1199
+ else {
1200
+ const base = extract_base(lemma, ending);
1201
+ if (base) {
1202
+ return [base, stem2, subtypes];
1203
+ }
1204
+ }
1205
+ }
1206
+ }
1207
+ if (decltype) {
1208
+ throw Error(`Unrecognized ending for declension-${decltype} noun: ${lemma}`);
1209
+ }
1210
+ return ["", "", []];
1211
+ }
1212
+ decline_segment_run(parsed_run, pos, is_adj) {
1213
+ const declensions = {
1214
+ forms: new Map(),
1215
+ notes: new Map(),
1216
+ title: [],
1217
+ subtitleses: [],
1218
+ orig_titles: [],
1219
+ categories: [],
1220
+ voc: true,
1221
+ noneut: false,
1222
+ nomf: false,
1223
+ };
1224
+ for (const slot of this.iter_slots(is_adj)) {
1225
+ setNominalForm(declensions.forms, slot, [""]);
1226
+ }
1227
+ for (const seg of parsed_run.segments) {
1228
+ if (seg.type == "Segment" && seg.decl) {
1229
+ seg.loc = parsed_run.loc;
1230
+ seg.num = seg.num || parsed_run.num;
1231
+ seg.gender = seg.gender || parsed_run.gender;
1232
+ let data;
1233
+ let potential_lemma_slots;
1234
+ if (seg.is_adj) {
1235
+ const decline = m_adj_decl.get(seg.decl);
1236
+ if (!decline) {
1237
+ throw Error(`Unrecognized declension '${seg.decl}'`);
1238
+ }
1239
+ potential_lemma_slots = this.potential_adj_lemma_slots;
1240
+ data = {
1241
+ declOpts: this.options,
1242
+ subtitles: [],
1243
+ footnote: "",
1244
+ num: seg.num,
1245
+ gender: seg.gender,
1246
+ loc: seg.loc,
1247
+ voc: true,
1248
+ noneut: false,
1249
+ nomf: false,
1250
+ pos: is_adj ? pos : "adjectives",
1251
+ forms: new Map(),
1252
+ types: seg.types,
1253
+ categories: [],
1254
+ notes: new Map(),
1255
+ };
1256
+ decline(data, seg.args);
1257
+ if (!data.voc) {
1258
+ declensions.voc = false;
1259
+ }
1260
+ if (data.noneut) {
1261
+ declensions.noneut = true;
1262
+ }
1263
+ if (data.nomf) {
1264
+ declensions.nomf = true;
1265
+ }
1266
+ if (data.types.has(NominalType.SuffixN)) {
1267
+ data.subtitles.push(["with", " 'm' optionally → 'n' in compounds"]);
1268
+ }
1269
+ else if (data.types.has(NominalType.NotSuffixN)) {
1270
+ data.subtitles.push(["without", " 'm' optionally → 'n' in compounds"]);
1271
+ }
1272
+ if (data.title) {
1273
+ declensions.orig_titles.push(data.title);
1274
+ }
1275
+ if (data.subtitles.length > 0) {
1276
+ const subtitles = [];
1277
+ for (const subtitle of data.subtitles) {
1278
+ if (typeof (subtitle) == "string") {
1279
+ subtitles.push(subtitle);
1280
+ }
1281
+ else {
1282
+ subtitles.push(subtitle.join(""));
1283
+ }
1284
+ }
1285
+ data.title = (data.title ?? "") + " (" + subtitles.join(", ") + ")";
1286
+ }
1287
+ for (const subtitle of data.subtitles) {
1288
+ declensions.subtitleses.push(subtitle);
1289
+ }
1290
+ }
1291
+ else {
1292
+ const decline = m_noun_decl.get(seg.decl);
1293
+ if (!decline) {
1294
+ throw Error(`Unrecognized declension '${seg.decl}'`);
1295
+ }
1296
+ potential_lemma_slots = this.potential_noun_lemma_slots;
1297
+ data = {
1298
+ declOpts: this.options,
1299
+ subtitles: [],
1300
+ footnote: "",
1301
+ num: seg.num,
1302
+ loc: seg.loc,
1303
+ pos: pos,
1304
+ forms: new Map(),
1305
+ types: seg.types,
1306
+ categories: [],
1307
+ notes: new Map(),
1308
+ };
1309
+ decline(data, seg.args);
1310
+ const apparent_decl = data.decl || this.irreg_noun_to_decl.get(seg.lemma) || seg.decl;
1311
+ if (seg.props) {
1312
+ seg.props.headword_decl = apparent_decl;
1313
+ }
1314
+ if (!data.title) {
1315
+ if (seg.decl == "irreg" && apparent_decl != "irreg" && data.subtitles.length == 0) {
1316
+ data.subtitles.push("[[Appendix:Glossary#irregular|irregular]]");
1317
+ }
1318
+ const english = this.declension_to_english.get(apparent_decl);
1319
+ if (english) {
1320
+ data.title = `${english}-declension`;
1321
+ }
1322
+ else if (apparent_decl == "irreg") {
1323
+ data.title = "irregular";
1324
+ }
1325
+ else if (apparent_decl == "indecl" || apparent_decl == "0" || apparent_decl == "sgpl") {
1326
+ data.title = "indeclinable";
1327
+ }
1328
+ else {
1329
+ throw Error(`Internal error! Don't recognize noun declension ${apparent_decl}`);
1330
+ }
1331
+ data.title = data.title + " noun";
1332
+ }
1333
+ if (data.types.has(NominalType.SuffixN)) {
1334
+ data.subtitles.push(["with", " 'm' optionally → 'n' in compounds"]);
1335
+ }
1336
+ else if (data.types.has(NominalType.NotSuffixN)) {
1337
+ data.subtitles.push(["without", " 'm' optionally → 'n' in compounds"]);
1338
+ }
1339
+ declensions.orig_titles.push(data.title);
1340
+ if (data.subtitles.length > 0) {
1341
+ const subtitles = [];
1342
+ for (const subtitle of data.subtitles) {
1343
+ if (typeof (subtitle) == "string") {
1344
+ subtitles.push(subtitle);
1345
+ }
1346
+ else {
1347
+ subtitles.push(subtitle.join(""));
1348
+ }
1349
+ }
1350
+ data.title = data.title + ` (${subtitles.join(", ")})`;
1351
+ }
1352
+ for (const subtitle of data.subtitles) {
1353
+ declensions.subtitleses.push(subtitle);
1354
+ }
1355
+ }
1356
+ for (const slot of potential_lemma_slots) {
1357
+ const forms = getNominalForm(data.forms, slot);
1358
+ if (forms) {
1359
+ const linked_forms = [];
1360
+ for (const form of forms) {
1361
+ if (form == seg.lemma) {
1362
+ linked_forms.push(seg.orig_lemma);
1363
+ }
1364
+ else {
1365
+ linked_forms.push(form);
1366
+ }
1367
+ }
1368
+ setNominalForm(data.forms, `linked_${slot}`, linked_forms);
1369
+ }
1370
+ }
1371
+ if (seg.types.has(NominalType.Ligature)) {
1372
+ this.apply_ligatures(data.forms, is_adj);
1373
+ }
1374
+ if (seg.types.has(NominalType.SuffixN)) {
1375
+ this.apply_sufn(data.forms, is_adj);
1376
+ }
1377
+ this.propagate_number_restrictions(data.forms, seg.num, is_adj);
1378
+ for (const slot of this.iter_slots(is_adj)) {
1379
+ let new_forms;
1380
+ if (is_adj) {
1381
+ if (!seg.is_adj) {
1382
+ throw Error(`Can't decline noun '${seg.lemma}' when overall term is an adjective`);
1383
+ }
1384
+ new_forms = getNominalForm(data.forms, slot);
1385
+ if (!new_forms && slot.match(/_[fn]$/)) {
1386
+ new_forms = getNominalForm(data.forms, slot.replace(/_[fn]$/, "_m"));
1387
+ }
1388
+ }
1389
+ else if (seg.is_adj) {
1390
+ if (!seg.gender) {
1391
+ throw Error(`Declining modifying adjective ${seg.lemma} but don't know gender of associated noun`);
1392
+ }
1393
+ new_forms = getNominalForm(data.forms, slot + "_" + seg.gender.toLowerCase()) ||
1394
+ getNominalForm(data.forms, slot + "_m");
1395
+ }
1396
+ else {
1397
+ new_forms = getNominalForm(data.forms, slot);
1398
+ }
1399
+ const new_notes = [];
1400
+ if (new_forms) {
1401
+ for (let j = 0; j < new_forms.length; j++) {
1402
+ const noteses = data.notes.get(`${slot}${j + 1}`);
1403
+ if (noteses) {
1404
+ new_notes[j] = [noteses];
1405
+ }
1406
+ }
1407
+ }
1408
+ const oldForms = getNominalForm(declensions.forms, slot);
1409
+ const [forms, notes] = this.append_form(oldForms, declensions.notes.get(slot), new_forms, new_notes, slot.includes("linked") ? seg.orig_prefix : seg.prefix);
1410
+ setNominalForm(declensions.forms, slot, forms);
1411
+ declensions.notes.set(slot, notes);
1412
+ }
1413
+ if (!seg.types.has(NominalType.NoCategories) && (is_adj || !seg.is_adj)) {
1414
+ for (const cat of data.categories) {
1415
+ this.insert_if_not(declensions.categories, cat);
1416
+ }
1417
+ }
1418
+ if (seg.prefix != "" && seg.prefix != "-" && seg.prefix != " ") {
1419
+ declensions.title.push("[[Appendix:Glossary#indeclinable|indeclinable]] portion");
1420
+ }
1421
+ if (data.title) {
1422
+ declensions.title.push(data.title);
1423
+ }
1424
+ }
1425
+ else if (seg.type == "Alternant") {
1426
+ let seg_declensions;
1427
+ const seg_titles = [];
1428
+ const seg_subtitleses = [];
1429
+ const seg_stems_seen = [];
1430
+ const seg_categories = [];
1431
+ let title_the_hard_way = false;
1432
+ let alternant_decl = "";
1433
+ let alternant_decl_title;
1434
+ for (const this_parsed_run of seg.alternants) {
1435
+ let num_non_constant_segments = 0;
1436
+ for (const segment of (this_parsed_run.segments)) {
1437
+ if (segment.type == "Segment" && segment.decl) {
1438
+ if (!alternant_decl) {
1439
+ alternant_decl = segment.decl;
1440
+ }
1441
+ else if (alternant_decl != segment.decl) {
1442
+ title_the_hard_way = true;
1443
+ num_non_constant_segments = 500;
1444
+ break;
1445
+ }
1446
+ num_non_constant_segments++;
1447
+ }
1448
+ }
1449
+ if (num_non_constant_segments != 1) {
1450
+ title_the_hard_way = true;
1451
+ }
1452
+ }
1453
+ if (!title_the_hard_way) {
1454
+ const subtypeses = new Set();
1455
+ for (const this_parsed_run of seg.alternants) {
1456
+ for (const segment of this_parsed_run.segments) {
1457
+ if (segment.type == "Segment" && segment.decl) {
1458
+ segment.types.forEach(t => subtypeses.add(t));
1459
+ this.insert_if_not(seg_stems_seen, segment.stem2 || "");
1460
+ }
1461
+ }
1462
+ }
1463
+ for (const this_parsed_run of seg.alternants) {
1464
+ for (const segment of this_parsed_run.segments) {
1465
+ if (segment.type == "Segment" && segment.decl) {
1466
+ const neg_subtypes = this.set_difference(subtypeses, segment.types);
1467
+ for (const neg_subtype of neg_subtypes) {
1468
+ addNominalType(segment.types, "not_" + neg_subtype);
1469
+ }
1470
+ }
1471
+ }
1472
+ }
1473
+ }
1474
+ for (const this_parsed_run of seg.alternants) {
1475
+ this_parsed_run.loc = seg.loc;
1476
+ this_parsed_run.num = this_parsed_run.num || seg.num;
1477
+ this_parsed_run.gender = this_parsed_run.gender || seg.gender;
1478
+ const this_declensions = this.decline_segment_run(this_parsed_run, pos, is_adj);
1479
+ if (!this_declensions.voc) {
1480
+ declensions.voc = false;
1481
+ }
1482
+ if (this_declensions.noneut) {
1483
+ declensions.noneut = true;
1484
+ }
1485
+ if (this_declensions.nomf) {
1486
+ declensions.nomf = true;
1487
+ }
1488
+ if (this_parsed_run.num == "sg" || this_parsed_run.num == "pl") {
1489
+ for (const slot of (this.iter_slots(is_adj))) {
1490
+ if ((this_parsed_run.num == "sg" && slot.includes("pl")) ||
1491
+ (this_parsed_run.num == "pl" && slot.includes("sg"))) {
1492
+ setNominalForm(this_declensions.forms, slot, []);
1493
+ this_declensions.notes.delete(slot);
1494
+ }
1495
+ }
1496
+ }
1497
+ if (!seg_declensions) {
1498
+ seg_declensions = this_declensions;
1499
+ }
1500
+ else {
1501
+ for (const slot of this.iter_slots(is_adj)) {
1502
+ const curforms = getNominalForm(seg_declensions.forms, slot) || [];
1503
+ const newforms = getNominalForm(this_declensions.forms, slot) || [];
1504
+ const newform_index_to_new_index = [];
1505
+ newforms.forEach((form, newj) => {
1506
+ let did_break = false;
1507
+ for (let j = 0; j < curforms.length; j++) {
1508
+ if (curforms[j] == form) {
1509
+ newform_index_to_new_index[newj] = j;
1510
+ did_break = true;
1511
+ break;
1512
+ }
1513
+ }
1514
+ if (!did_break) {
1515
+ curforms.push(form);
1516
+ newform_index_to_new_index[newj] = curforms.length - 1;
1517
+ }
1518
+ });
1519
+ setNominalForm(seg_declensions.forms, slot, curforms);
1520
+ const curnotes = seg_declensions.notes.get(slot) || [];
1521
+ const newnotes = this_declensions.notes.get(slot);
1522
+ if (newnotes) {
1523
+ newnotes.forEach((notes, index) => {
1524
+ const combined_index = newform_index_to_new_index[index];
1525
+ if (!curnotes[combined_index]) {
1526
+ curnotes[combined_index] = notes;
1527
+ }
1528
+ else {
1529
+ const combined = Array.from(curnotes[combined_index]);
1530
+ for (const note of notes) {
1531
+ this.insert_if_not(combined, note);
1532
+ }
1533
+ curnotes[combined_index] = combined;
1534
+ }
1535
+ });
1536
+ seg_declensions.notes.set(slot, curnotes);
1537
+ }
1538
+ }
1539
+ }
1540
+ for (const cat of this_declensions.categories) {
1541
+ this.insert_if_not(seg_categories, cat);
1542
+ }
1543
+ this.insert_if_not(seg_titles, this_declensions.title.join(""));
1544
+ seg_subtitleses.push(this_declensions.subtitleses);
1545
+ if (!alternant_decl_title) {
1546
+ alternant_decl_title = this_declensions.orig_titles[0];
1547
+ }
1548
+ }
1549
+ if (!seg_declensions) {
1550
+ throw Error("No segment declensions");
1551
+ }
1552
+ this.propagate_number_restrictions(seg_declensions?.forms, parsed_run.num, is_adj);
1553
+ for (const slot of this.iter_slots(is_adj)) {
1554
+ const declForms = getNominalForm(declensions.forms, slot);
1555
+ const segForms = getNominalForm(seg_declensions.forms, slot);
1556
+ const [newForms, notes] = this.append_form(declForms, declensions.notes.get(slot), segForms, seg_declensions.notes.get(slot), undefined);
1557
+ setNominalForm(declensions.forms, slot, newForms);
1558
+ declensions.notes.set(slot, notes);
1559
+ }
1560
+ if (is_adj || !seg.is_adj) {
1561
+ for (const cat of seg_categories) {
1562
+ this.insert_if_not(declensions.categories, cat);
1563
+ }
1564
+ }
1565
+ let title_to_insert;
1566
+ if (title_the_hard_way) {
1567
+ title_to_insert = this.join_sentences(seg_titles, " or ");
1568
+ }
1569
+ else {
1570
+ const first = seg_subtitleses[0];
1571
+ if (typeof (first) == "string") {
1572
+ throw Error("Expected multi-title");
1573
+ }
1574
+ const first_subtitles = first;
1575
+ let num_common_subtitles = first_subtitles.length;
1576
+ for (let i = 1; i < seg_subtitleses.length; i++) {
1577
+ const this_subtitles = seg_subtitleses[i];
1578
+ for (let j = 0; j < num_common_subtitles; j++) {
1579
+ if (first_subtitles[j] != this_subtitles[j]) {
1580
+ num_common_subtitles = j;
1581
+ break;
1582
+ }
1583
+ }
1584
+ }
1585
+ const common_subtitles = [];
1586
+ for (let i = 0; i < num_common_subtitles; i++) {
1587
+ const entry = first_subtitles[i];
1588
+ if (typeof (entry) != "string") {
1589
+ common_subtitles.push(entry.join(""));
1590
+ }
1591
+ else {
1592
+ common_subtitles.push(entry);
1593
+ }
1594
+ }
1595
+ const common_subtitle_portion = common_subtitles.join(", ");
1596
+ let non_common_subtitle_portion;
1597
+ let common_prefix;
1598
+ let common_suffix;
1599
+ for (let i = 0; i < seg_subtitleses.length; i++) {
1600
+ const this_subtitles = seg_subtitleses[i];
1601
+ if (typeof (this_subtitles) == "string") {
1602
+ throw Error("Expected subtitles to be array");
1603
+ }
1604
+ if (this_subtitles.length != num_common_subtitles + 1 || typeof (this_subtitles[num_common_subtitles]) == "string" || this_subtitles[num_common_subtitles].length != 2) {
1605
+ break;
1606
+ }
1607
+ if (i == 0) {
1608
+ common_prefix = this_subtitles[num_common_subtitles][0];
1609
+ common_suffix = this_subtitles[num_common_subtitles][1];
1610
+ }
1611
+ else {
1612
+ const this_prefix = this_subtitles[num_common_subtitles][0];
1613
+ const this_suffix = this_subtitles[num_common_subtitles][1];
1614
+ if (this_prefix != common_prefix) {
1615
+ common_prefix = undefined;
1616
+ }
1617
+ if (this_suffix != common_suffix) {
1618
+ common_suffix = undefined;
1619
+ }
1620
+ if (!common_prefix && !common_suffix) {
1621
+ break;
1622
+ }
1623
+ }
1624
+ }
1625
+ if (common_prefix || common_suffix) {
1626
+ if (common_prefix && common_suffix) {
1627
+ throw Error("Something is wrong, first non-common subtitle is actually common to all segments");
1628
+ }
1629
+ if (common_prefix) {
1630
+ const non_common_parts = [];
1631
+ for (const subtitles of seg_subtitleses) {
1632
+ non_common_parts.push(subtitles[num_common_subtitles][1]);
1633
+ }
1634
+ non_common_subtitle_portion = common_prefix + non_common_parts.join(" or ");
1635
+ }
1636
+ else {
1637
+ const non_common_parts = [];
1638
+ for (const subtitles of seg_subtitleses) {
1639
+ non_common_parts.push(subtitles[num_common_subtitles][0]);
1640
+ }
1641
+ non_common_subtitle_portion = non_common_parts.join(" or ") + (common_suffix ?? "");
1642
+ }
1643
+ }
1644
+ else {
1645
+ let saw_non_common_subtitles = false;
1646
+ const non_common_subtitles = [];
1647
+ for (const this_subtitles of seg_subtitleses) {
1648
+ const this_non_common_subtitles = [];
1649
+ for (let j = num_common_subtitles; j < this_subtitles.length; j++) {
1650
+ this_non_common_subtitles.push(this_subtitles[j]);
1651
+ }
1652
+ if (this_non_common_subtitles.length > 0) {
1653
+ non_common_subtitles.push(this_non_common_subtitles.join(", "));
1654
+ saw_non_common_subtitles = true;
1655
+ }
1656
+ else {
1657
+ non_common_subtitles.push("otherwise");
1658
+ }
1659
+ }
1660
+ non_common_subtitle_portion = saw_non_common_subtitles ? non_common_subtitles.join(" or ") : "";
1661
+ }
1662
+ const subtitle_portions = [];
1663
+ if (common_subtitle_portion) {
1664
+ subtitle_portions.push(common_subtitle_portion);
1665
+ }
1666
+ if (non_common_subtitle_portion) {
1667
+ subtitle_portions.push(non_common_subtitle_portion);
1668
+ }
1669
+ if (seg_stems_seen.length > 1) {
1670
+ const number_to_english = [
1671
+ "zero", "one", "two", "three", "four", "five"
1672
+ ];
1673
+ subtitle_portions.push((number_to_english[seg_stems_seen.length] || `${seg_stems_seen.length}`) + " different stems");
1674
+ }
1675
+ const subtitle_portion = subtitle_portions.join("; ");
1676
+ if (subtitle_portion) {
1677
+ title_to_insert = (alternant_decl_title ?? "") + " (" + subtitle_portion + ")";
1678
+ }
1679
+ else {
1680
+ title_to_insert = alternant_decl_title;
1681
+ }
1682
+ }
1683
+ if (title_to_insert) {
1684
+ declensions.title.push(title_to_insert);
1685
+ }
1686
+ }
1687
+ else {
1688
+ for (const slot of this.iter_slots(is_adj)) {
1689
+ const prefix = slot.includes("linked") ? seg.orig_prefix : seg.prefix;
1690
+ const [forms, notes] = this.append_form(getNominalForm(declensions.forms, slot), declensions.notes.get(slot), [prefix || ""], undefined, undefined);
1691
+ setNominalForm(declensions.forms, slot, forms);
1692
+ declensions.notes.set(slot, notes);
1693
+ }
1694
+ declensions.title.push("[[Appendix:Glossary#indeclinable|indeclinable]] portion");
1695
+ }
1696
+ }
1697
+ const titles = [];
1698
+ declensions.title.forEach((title, i) => {
1699
+ if (i == 0) {
1700
+ titles.push(this.uppercase_first_visible(title));
1701
+ }
1702
+ else {
1703
+ titles.push(this.add_indefinite_article(title));
1704
+ }
1705
+ });
1706
+ declensions.title = [titles.join(" with ")];
1707
+ return declensions;
1708
+ }
1709
+ add_indefinite_article(text) {
1710
+ // Determine the first visible character (display text of wikilink if present)
1711
+ // to choose "a" vs "an". Do NOT lowercase: Lua's add_indefinite_article passes
1712
+ // the text through unchanged and only prepends the article.
1713
+ let first_visible = text;
1714
+ const wikilink_match = text.match(/^\[\[[^\]]*\|([^\]]+)/);
1715
+ if (wikilink_match) {
1716
+ first_visible = wikilink_match[1];
1717
+ }
1718
+ if (first_visible.match(/^[aeiou]/i)) {
1719
+ return `an ${text}`;
1720
+ }
1721
+ else {
1722
+ return `a ${text}`;
1723
+ }
1724
+ }
1725
+ uppercase_first_visible(text) {
1726
+ // Handle titles that begin with a wikilink: [[...|text]]
1727
+ if (text.match(/^\[\[[^\]]*\|[a-z]/)) {
1728
+ return text.replace(/^(\[\[[^\]]*\|)([a-z])/, (_m, prefix, first) => `${prefix}${first.toUpperCase()}`);
1729
+ }
1730
+ if (text.match(/^[a-z]/)) {
1731
+ return text[0].toUpperCase() + text.slice(1);
1732
+ }
1733
+ return text;
1734
+ }
1735
+ lowercase_first_visible(text) {
1736
+ // Handle titles that begin with a wikilink: [[...|Text]]
1737
+ if (text.match(/^\[\[[^\]]*\|[A-Z]/)) {
1738
+ return text.replace(/^(\[\[[^\]]*\|)([A-Z])/, (_m, prefix, first) => `${prefix}${first.toLowerCase()}`);
1739
+ }
1740
+ if (text.match(/^[A-Z]/)) {
1741
+ return text[0].toLowerCase() + text.slice(1);
1742
+ }
1743
+ return text;
1744
+ }
1745
+ join_sentences(sentences, joiner) {
1746
+ const sentences_to_join = [];
1747
+ sentences.forEach((sentence, i) => {
1748
+ if (i < sentences.length - 1) {
1749
+ sentence = sentence.replace(/\.$/, "");
1750
+ }
1751
+ if (i > 0) {
1752
+ sentence = sentence[0].toLowerCase() + sentence.slice(1);
1753
+ }
1754
+ sentences_to_join.push(sentence);
1755
+ });
1756
+ return sentences_to_join.join(joiner);
1757
+ }
1758
+ set_difference(a, b) {
1759
+ const res = new Set();
1760
+ for (const key of a.keys()) {
1761
+ if (!b.has(key)) {
1762
+ res.add(key);
1763
+ }
1764
+ }
1765
+ return res;
1766
+ }
1767
+ append_form(forms, notes, new_forms, new_notes, prefix) {
1768
+ forms = forms || [];
1769
+ new_forms = new_forms || [];
1770
+ notes = notes || [];
1771
+ new_notes = new_notes || [];
1772
+ prefix = prefix || "";
1773
+ if (new_forms.length == 1) {
1774
+ for (let i = 0; i < forms.length; i++) {
1775
+ const base = (forms[i] == LaNominal.EmptyForm && (prefix || (new_forms[0] && new_forms[0] != LaNominal.EmptyForm))) ? "-" : forms[i];
1776
+ const suffix = (base && base != LaNominal.EmptyForm && new_forms[0] == LaNominal.EmptyForm) ? "-" : new_forms[0];
1777
+ forms[i] = base + prefix + suffix;
1778
+ if (new_notes[0]) {
1779
+ if (!notes[i]) {
1780
+ notes[i] = new_notes[0];
1781
+ }
1782
+ else {
1783
+ const combined_notes = Array.from(notes[i]);
1784
+ for (const note of new_notes[0]) {
1785
+ combined_notes.push(note);
1786
+ }
1787
+ notes[i] = combined_notes;
1788
+ }
1789
+ }
1790
+ }
1791
+ return [forms, notes];
1792
+ }
1793
+ else {
1794
+ const ret_forms = [];
1795
+ const ret_notes = [];
1796
+ for (let i = 0; i < forms.length; i++) {
1797
+ for (let j = 0; j < new_forms.length; j++) {
1798
+ const base = (forms[i] == LaNominal.EmptyForm && (prefix || (new_forms[j] && new_forms[j] != LaNominal.EmptyForm))) ? "-" : forms[i];
1799
+ const suffix = (base && base != LaNominal.EmptyForm && new_forms[j] == LaNominal.EmptyForm) ? "-" : new_forms[j];
1800
+ ret_forms.push(base + prefix + suffix);
1801
+ if (new_notes[j]) {
1802
+ if (!notes[i]) {
1803
+ ret_notes[i * new_forms.length + j] = new_notes[j];
1804
+ }
1805
+ else {
1806
+ const combined_notes = Array.from(notes[i]);
1807
+ for (const note of new_notes[j]) {
1808
+ combined_notes.push(note);
1809
+ }
1810
+ ret_notes[i * new_forms.length + j] = combined_notes;
1811
+ }
1812
+ }
1813
+ }
1814
+ }
1815
+ return [ret_forms, ret_notes];
1816
+ }
1817
+ }
1818
+ apply_ligatures(forms, is_adj) {
1819
+ for (const slot of this.iter_slots(is_adj)) {
1820
+ const ffs = getNominalForm(forms, slot) || [];
1821
+ for (let i = 0; i < ffs.length; i++) {
1822
+ ffs[i] = ffs[i].replace(/Ae/g, "Æ");
1823
+ ffs[i] = ffs[i].replace(/Oe/g, "Œ");
1824
+ ffs[i] = ffs[i].replace(/ae/g, "æ");
1825
+ ffs[i] = ffs[i].replace(/oe/g, "œ");
1826
+ }
1827
+ setNominalForm(forms, slot, ffs);
1828
+ }
1829
+ }
1830
+ apply_sufn(forms, is_adj) {
1831
+ for (const slot of this.iter_slots(is_adj)) {
1832
+ const ffs = getNominalForm(forms, slot) || [];
1833
+ if (ffs.length == 1 && !slot.includes("linked")) {
1834
+ const form = ffs[0];
1835
+ if (form.match(/m$/)) {
1836
+ const keep = this.options.suppressSufNM ? [] : ffs;
1837
+ setNominalForm(forms, slot, [form.replace(/m$/, "n"), ...keep]);
1838
+ }
1839
+ }
1840
+ else {
1841
+ let final_m = false;
1842
+ for (const form of getNominalForm(forms, slot) || []) {
1843
+ if (form.match(/m$/)) {
1844
+ final_m = true;
1845
+ }
1846
+ }
1847
+ if (final_m) {
1848
+ const newval = [];
1849
+ for (const form of getNominalForm(forms, slot) || []) {
1850
+ if (form.match(/m$/)) {
1851
+ const val = form.replace(/m$/, "n");
1852
+ newval.push(val);
1853
+ }
1854
+ if (!this.options.suppressSufNM) {
1855
+ newval.push(form);
1856
+ }
1857
+ }
1858
+ setNominalForm(forms, slot, newval);
1859
+ }
1860
+ }
1861
+ }
1862
+ }
1863
+ propagate_number_restrictions(forms, num, is_adj) {
1864
+ if (num == "sg" || num == "pl") {
1865
+ for (const slot of this.iter_slots(is_adj)) {
1866
+ if (slot.match(num)) {
1867
+ const other_num_slot = (num == "sg") ? slot.replace("sg", "pl") : slot.replace("pl", "sg");
1868
+ setNominalForm(forms, other_num_slot, getNominalForm(forms, slot) || []);
1869
+ }
1870
+ }
1871
+ }
1872
+ }
1873
+ insert_if_not(data, entry, pos = 0) {
1874
+ if (data.includes(entry)) {
1875
+ return;
1876
+ }
1877
+ if (pos == 0) {
1878
+ data.push(entry);
1879
+ }
1880
+ else {
1881
+ data.splice(pos - 1, 0, entry);
1882
+ }
1883
+ }
1884
+ }