@willwade/aac-processors 0.2.2 → 0.2.4

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 (50) hide show
  1. package/README.md +3 -1
  2. package/dist/browser/core/treeStructure.js +3 -1
  3. package/dist/browser/metrics.js +2 -0
  4. package/dist/browser/processors/astericsGridProcessor.js +21 -12
  5. package/dist/browser/processors/gridset/helpers.js +3 -3
  6. package/dist/browser/processors/gridsetProcessor.js +16 -8
  7. package/dist/browser/processors/obfProcessor.js +4 -4
  8. package/dist/browser/processors/snap/helpers.js +3 -3
  9. package/dist/browser/processors/snapProcessor.js +2 -2
  10. package/dist/browser/processors/touchchatProcessor.js +6 -6
  11. package/dist/browser/utilities/analytics/metrics/core.js +213 -8
  12. package/dist/browser/utilities/analytics/metrics/vocabulary.js +13 -1
  13. package/dist/browser/utilities/analytics/morphology/engine.js +910 -0
  14. package/dist/browser/utilities/analytics/morphology/grid3VerbsParser.js +455 -0
  15. package/dist/browser/utilities/analytics/morphology/index.js +3 -0
  16. package/dist/browser/utilities/analytics/morphology/types.js +1 -0
  17. package/dist/browser/utilities/analytics/morphology/wordFormGenerator.js +74 -0
  18. package/dist/browser/utils/sqlite.js +10 -6
  19. package/dist/core/treeStructure.d.ts +17 -1
  20. package/dist/core/treeStructure.js +3 -1
  21. package/dist/index.node.d.ts +2 -0
  22. package/dist/index.node.js +6 -1
  23. package/dist/metrics.d.ts +3 -0
  24. package/dist/metrics.js +5 -1
  25. package/dist/processors/astericsGridProcessor.js +21 -12
  26. package/dist/processors/excelProcessor.js +5 -1
  27. package/dist/processors/gridset/helpers.js +3 -3
  28. package/dist/processors/gridset/imageDebug.js +2 -2
  29. package/dist/processors/gridsetProcessor.js +16 -8
  30. package/dist/processors/obfProcessor.js +4 -4
  31. package/dist/processors/snap/helpers.js +3 -3
  32. package/dist/processors/snapProcessor.js +2 -2
  33. package/dist/processors/touchchatProcessor.js +6 -6
  34. package/dist/utilities/analytics/metrics/core.d.ts +14 -0
  35. package/dist/utilities/analytics/metrics/core.js +213 -8
  36. package/dist/utilities/analytics/metrics/types.d.ts +18 -3
  37. package/dist/utilities/analytics/metrics/vocabulary.d.ts +3 -0
  38. package/dist/utilities/analytics/metrics/vocabulary.js +13 -1
  39. package/dist/utilities/analytics/morphology/engine.d.ts +30 -0
  40. package/dist/utilities/analytics/morphology/engine.js +914 -0
  41. package/dist/utilities/analytics/morphology/grid3VerbsParser.d.ts +36 -0
  42. package/dist/utilities/analytics/morphology/grid3VerbsParser.js +485 -0
  43. package/dist/utilities/analytics/morphology/index.d.ts +5 -0
  44. package/dist/utilities/analytics/morphology/index.js +9 -0
  45. package/dist/utilities/analytics/morphology/types.d.ts +40 -0
  46. package/dist/utilities/analytics/morphology/types.js +2 -0
  47. package/dist/utilities/analytics/morphology/wordFormGenerator.d.ts +10 -0
  48. package/dist/utilities/analytics/morphology/wordFormGenerator.js +78 -0
  49. package/dist/utils/sqlite.js +10 -29
  50. package/package.json +13 -12
@@ -0,0 +1,910 @@
1
+ export class MorphologyEngine {
2
+ constructor(ruleSetOrLocale) {
3
+ this.cache = new Map();
4
+ if (typeof ruleSetOrLocale === 'string') {
5
+ this.ruleSet = this.loadBundled(ruleSetOrLocale);
6
+ }
7
+ else {
8
+ this.ruleSet = ruleSetOrLocale;
9
+ }
10
+ }
11
+ static fromGrid3Verbs(verbForms) {
12
+ const engine = new MorphologyEngine({
13
+ locale: verbForms.locale,
14
+ version: 1,
15
+ irregular: {},
16
+ regular: {},
17
+ });
18
+ engine.grid3Verbs = verbForms.verbs;
19
+ return engine;
20
+ }
21
+ get locale() {
22
+ return this.ruleSet.locale;
23
+ }
24
+ inflect(base, pos) {
25
+ const key = `${base.toLowerCase()}|${pos}`;
26
+ const cached = this.cache.get(key);
27
+ if (cached)
28
+ return cached;
29
+ if (this.grid3Verbs) {
30
+ const forms = this.grid3Verbs.get(base) || this.grid3Verbs.get(base.toLowerCase());
31
+ if (forms) {
32
+ this.cache.set(key, forms);
33
+ return forms;
34
+ }
35
+ }
36
+ const forms = this.computeForms(base, pos);
37
+ this.cache.set(key, forms);
38
+ return forms;
39
+ }
40
+ isFormOf(word, base, pos) {
41
+ const forms = this.inflect(base, pos);
42
+ const lower = word.toLowerCase();
43
+ return forms.some((f) => f.toLowerCase() === lower);
44
+ }
45
+ expandVocabulary(buttons) {
46
+ const result = new Map();
47
+ for (const btn of buttons) {
48
+ const pos = btn.pos;
49
+ if (!pos || pos === 'Unknown' || pos === 'Ignore')
50
+ continue;
51
+ const forms = this.inflect(btn.label, pos);
52
+ if (forms.length > 0) {
53
+ result.set(btn.label, forms);
54
+ }
55
+ }
56
+ return result;
57
+ }
58
+ inflectWithSlots(base, pos) {
59
+ const lower = base.toLowerCase();
60
+ const result = [];
61
+ const seen = new Set();
62
+ const irregular = this.ruleSet.irregular[pos]?.[lower];
63
+ const regularSlots = this.ruleSet.regular[pos];
64
+ if (irregular) {
65
+ for (const [slot, value] of Object.entries(irregular)) {
66
+ if (slot === 'extra' && Array.isArray(value)) {
67
+ value.forEach((v) => {
68
+ if (!seen.has(v)) {
69
+ seen.add(v);
70
+ result.push({ slot, form: v });
71
+ }
72
+ });
73
+ }
74
+ else if (Array.isArray(value)) {
75
+ value.forEach((v) => {
76
+ if (!seen.has(v)) {
77
+ seen.add(v);
78
+ result.push({ slot, form: v });
79
+ }
80
+ });
81
+ }
82
+ else {
83
+ if (!seen.has(value)) {
84
+ seen.add(value);
85
+ result.push({ slot, form: value });
86
+ }
87
+ }
88
+ }
89
+ }
90
+ if (!regularSlots)
91
+ return result;
92
+ for (const [slot, rulesOrAlias] of Object.entries(regularSlots)) {
93
+ if (irregular && irregular[slot] !== undefined)
94
+ continue;
95
+ let rules;
96
+ if (typeof rulesOrAlias === 'string') {
97
+ const aliased = regularSlots[rulesOrAlias];
98
+ if (!aliased || typeof aliased === 'string')
99
+ continue;
100
+ rules = aliased;
101
+ }
102
+ else {
103
+ rules = rulesOrAlias;
104
+ }
105
+ const form = this.applyRules(lower, rules);
106
+ if (form && form !== lower && !seen.has(form)) {
107
+ seen.add(form);
108
+ result.push({ slot, form });
109
+ }
110
+ }
111
+ return result;
112
+ }
113
+ computeForms(base, pos) {
114
+ const lower = base.toLowerCase();
115
+ const forms = new Set();
116
+ const irregular = this.ruleSet.irregular[pos]?.[lower];
117
+ const regularSlots = this.ruleSet.regular[pos];
118
+ if (irregular) {
119
+ for (const [slot, value] of Object.entries(irregular)) {
120
+ if (slot === 'extra' && Array.isArray(value)) {
121
+ value.forEach((v) => forms.add(v));
122
+ }
123
+ else if (Array.isArray(value)) {
124
+ value.forEach((v) => forms.add(v));
125
+ }
126
+ else {
127
+ forms.add(value);
128
+ }
129
+ }
130
+ }
131
+ if (!regularSlots) {
132
+ return Array.from(forms);
133
+ }
134
+ for (const [slot, rulesOrAlias] of Object.entries(regularSlots)) {
135
+ if (irregular && irregular[slot] !== undefined)
136
+ continue;
137
+ let rules;
138
+ if (typeof rulesOrAlias === 'string') {
139
+ const aliased = regularSlots[rulesOrAlias];
140
+ if (!aliased || typeof aliased === 'string')
141
+ continue;
142
+ rules = aliased;
143
+ }
144
+ else {
145
+ rules = rulesOrAlias;
146
+ }
147
+ const form = this.applyRules(lower, rules);
148
+ if (form && form !== lower) {
149
+ forms.add(form);
150
+ }
151
+ }
152
+ return Array.from(forms);
153
+ }
154
+ applyRules(word, rules) {
155
+ for (const rule of rules) {
156
+ const regex = new RegExp(rule.match, 'i');
157
+ if (regex.test(word)) {
158
+ return word.replace(new RegExp(rule.match, 'i'), rule.replace);
159
+ }
160
+ }
161
+ return undefined;
162
+ }
163
+ /**
164
+ * Infer the most likely POS for a word by checking the irregular tables.
165
+ * Returns the POS if found in any irregular table, or null if not found.
166
+ * Priority: Verb > Noun > Adjective > Pronoun
167
+ */
168
+ inferPOS(word) {
169
+ const lower = word.toLowerCase();
170
+ for (const pos of ['Verb', 'Noun', 'Adjective', 'Pronoun']) {
171
+ if (this.ruleSet.irregular[pos]?.[lower]) {
172
+ return pos;
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+ loadBundled(locale) {
178
+ const normalized = locale.toLowerCase().replace('_', '-');
179
+ switch (normalized) {
180
+ case 'en-gb':
181
+ case 'en-us':
182
+ case 'en-au':
183
+ case 'en-ca':
184
+ case 'en-nz':
185
+ case 'en-za':
186
+ case 'en':
187
+ return builtinEn();
188
+ default:
189
+ return { locale, version: 1, irregular: {}, regular: {} };
190
+ }
191
+ }
192
+ }
193
+ function builtinEn() {
194
+ return {
195
+ locale: 'en-gb',
196
+ version: 1,
197
+ irregular: {
198
+ Verb: {
199
+ be: {
200
+ '3sg': 'is',
201
+ past: 'was',
202
+ pastPart: 'been',
203
+ presPart: 'being',
204
+ extra: ['am', 'are', 'were'],
205
+ },
206
+ have: {
207
+ '3sg': 'has',
208
+ past: 'had',
209
+ pastPart: 'had',
210
+ presPart: 'having',
211
+ },
212
+ do: { '3sg': 'does', past: 'did', pastPart: 'done', presPart: 'doing' },
213
+ go: {
214
+ '3sg': 'goes',
215
+ past: 'went',
216
+ pastPart: 'gone',
217
+ presPart: 'going',
218
+ },
219
+ say: {
220
+ '3sg': 'says',
221
+ past: 'said',
222
+ pastPart: 'said',
223
+ presPart: 'saying',
224
+ },
225
+ get: {
226
+ '3sg': 'gets',
227
+ past: 'got',
228
+ pastPart: 'got',
229
+ presPart: 'getting',
230
+ },
231
+ make: {
232
+ '3sg': 'makes',
233
+ past: 'made',
234
+ pastPart: 'made',
235
+ presPart: 'making',
236
+ },
237
+ come: {
238
+ '3sg': 'comes',
239
+ past: 'came',
240
+ pastPart: 'come',
241
+ presPart: 'coming',
242
+ },
243
+ take: {
244
+ '3sg': 'takes',
245
+ past: 'took',
246
+ pastPart: 'taken',
247
+ presPart: 'taking',
248
+ },
249
+ know: {
250
+ '3sg': 'knows',
251
+ past: 'knew',
252
+ pastPart: 'known',
253
+ presPart: 'knowing',
254
+ },
255
+ think: {
256
+ '3sg': 'thinks',
257
+ past: 'thought',
258
+ pastPart: 'thought',
259
+ presPart: 'thinking',
260
+ },
261
+ see: {
262
+ '3sg': 'sees',
263
+ past: 'saw',
264
+ pastPart: 'seen',
265
+ presPart: 'seeing',
266
+ },
267
+ give: {
268
+ '3sg': 'gives',
269
+ past: 'gave',
270
+ pastPart: 'given',
271
+ presPart: 'giving',
272
+ },
273
+ find: {
274
+ '3sg': 'finds',
275
+ past: 'found',
276
+ pastPart: 'found',
277
+ presPart: 'finding',
278
+ },
279
+ tell: {
280
+ '3sg': 'tells',
281
+ past: 'told',
282
+ pastPart: 'told',
283
+ presPart: 'telling',
284
+ },
285
+ feel: {
286
+ '3sg': 'feels',
287
+ past: 'felt',
288
+ pastPart: 'felt',
289
+ presPart: 'feeling',
290
+ },
291
+ run: {
292
+ '3sg': 'runs',
293
+ past: 'ran',
294
+ pastPart: 'run',
295
+ presPart: 'running',
296
+ },
297
+ fly: {
298
+ '3sg': 'flies',
299
+ past: 'flew',
300
+ pastPart: 'flown',
301
+ presPart: 'flying',
302
+ },
303
+ try: {
304
+ '3sg': 'tries',
305
+ past: 'tried',
306
+ pastPart: 'tried',
307
+ presPart: 'trying',
308
+ },
309
+ leave: {
310
+ '3sg': 'leaves',
311
+ past: 'left',
312
+ pastPart: 'left',
313
+ presPart: 'leaving',
314
+ },
315
+ call: {
316
+ '3sg': 'calls',
317
+ past: 'called',
318
+ pastPart: 'called',
319
+ presPart: 'calling',
320
+ },
321
+ ask: {
322
+ '3sg': 'asks',
323
+ past: 'asked',
324
+ pastPart: 'asked',
325
+ presPart: 'asking',
326
+ },
327
+ put: {
328
+ '3sg': 'puts',
329
+ past: 'put',
330
+ pastPart: 'put',
331
+ presPart: 'putting',
332
+ },
333
+ read: {
334
+ '3sg': 'reads',
335
+ past: 'read',
336
+ pastPart: 'read',
337
+ presPart: 'reading',
338
+ },
339
+ eat: {
340
+ '3sg': 'eats',
341
+ past: 'ate',
342
+ pastPart: 'eaten',
343
+ presPart: 'eating',
344
+ },
345
+ drink: {
346
+ '3sg': 'drinks',
347
+ past: 'drank',
348
+ pastPart: 'drunk',
349
+ presPart: 'drinking',
350
+ },
351
+ sleep: {
352
+ '3sg': 'sleeps',
353
+ past: 'slept',
354
+ pastPart: 'slept',
355
+ presPart: 'sleeping',
356
+ },
357
+ speak: {
358
+ '3sg': 'speaks',
359
+ past: 'spoke',
360
+ pastPart: 'spoken',
361
+ presPart: 'speaking',
362
+ },
363
+ write: {
364
+ '3sg': 'writes',
365
+ past: 'wrote',
366
+ pastPart: 'written',
367
+ presPart: 'writing',
368
+ },
369
+ sit: {
370
+ '3sg': 'sits',
371
+ past: 'sat',
372
+ pastPart: 'sat',
373
+ presPart: 'sitting',
374
+ },
375
+ stand: {
376
+ '3sg': 'stands',
377
+ past: 'stood',
378
+ pastPart: 'stood',
379
+ presPart: 'standing',
380
+ },
381
+ fall: {
382
+ '3sg': 'falls',
383
+ past: 'fell',
384
+ pastPart: 'fallen',
385
+ presPart: 'falling',
386
+ },
387
+ hold: {
388
+ '3sg': 'holds',
389
+ past: 'held',
390
+ pastPart: 'held',
391
+ presPart: 'holding',
392
+ },
393
+ keep: {
394
+ '3sg': 'keeps',
395
+ past: 'kept',
396
+ pastPart: 'kept',
397
+ presPart: 'keeping',
398
+ },
399
+ buy: {
400
+ '3sg': 'buys',
401
+ past: 'bought',
402
+ pastPart: 'bought',
403
+ presPart: 'buying',
404
+ },
405
+ bring: {
406
+ '3sg': 'brings',
407
+ past: 'brought',
408
+ pastPart: 'brought',
409
+ presPart: 'bringing',
410
+ },
411
+ catch: {
412
+ '3sg': 'catches',
413
+ past: 'caught',
414
+ pastPart: 'caught',
415
+ presPart: 'catching',
416
+ },
417
+ teach: {
418
+ '3sg': 'teaches',
419
+ past: 'taught',
420
+ pastPart: 'taught',
421
+ presPart: 'teaching',
422
+ },
423
+ fight: {
424
+ '3sg': 'fights',
425
+ past: 'fought',
426
+ pastPart: 'fought',
427
+ presPart: 'fighting',
428
+ },
429
+ swim: {
430
+ '3sg': 'swims',
431
+ past: 'swam',
432
+ pastPart: 'swum',
433
+ presPart: 'swimming',
434
+ },
435
+ sing: {
436
+ '3sg': 'sings',
437
+ past: 'sang',
438
+ pastPart: 'sung',
439
+ presPart: 'singing',
440
+ },
441
+ draw: {
442
+ '3sg': 'draws',
443
+ past: 'drew',
444
+ pastPart: 'drawn',
445
+ presPart: 'drawing',
446
+ },
447
+ drive: {
448
+ '3sg': 'drives',
449
+ past: 'drove',
450
+ pastPart: 'driven',
451
+ presPart: 'driving',
452
+ },
453
+ ride: {
454
+ '3sg': 'rides',
455
+ past: 'rode',
456
+ pastPart: 'ridden',
457
+ presPart: 'riding',
458
+ },
459
+ grow: {
460
+ '3sg': 'grows',
461
+ past: 'grew',
462
+ pastPart: 'grown',
463
+ presPart: 'growing',
464
+ },
465
+ throw: {
466
+ '3sg': 'throws',
467
+ past: 'threw',
468
+ pastPart: 'thrown',
469
+ presPart: 'throwing',
470
+ },
471
+ break: {
472
+ '3sg': 'breaks',
473
+ past: 'broke',
474
+ pastPart: 'broken',
475
+ presPart: 'breaking',
476
+ },
477
+ wake: {
478
+ '3sg': 'wakes',
479
+ past: 'woke',
480
+ pastPart: 'woken',
481
+ presPart: 'waking',
482
+ },
483
+ wear: {
484
+ '3sg': 'wears',
485
+ past: 'wore',
486
+ pastPart: 'worn',
487
+ presPart: 'wearing',
488
+ },
489
+ win: {
490
+ '3sg': 'wins',
491
+ past: 'won',
492
+ pastPart: 'won',
493
+ presPart: 'winning',
494
+ },
495
+ choose: {
496
+ '3sg': 'chooses',
497
+ past: 'chose',
498
+ pastPart: 'chosen',
499
+ presPart: 'choosing',
500
+ },
501
+ hide: {
502
+ '3sg': 'hides',
503
+ past: 'hid',
504
+ pastPart: 'hidden',
505
+ presPart: 'hiding',
506
+ },
507
+ steal: {
508
+ '3sg': 'steals',
509
+ past: 'stole',
510
+ pastPart: 'stolen',
511
+ presPart: 'stealing',
512
+ },
513
+ begin: {
514
+ '3sg': 'begins',
515
+ past: 'began',
516
+ pastPart: 'begun',
517
+ presPart: 'beginning',
518
+ },
519
+ ring: {
520
+ '3sg': 'rings',
521
+ past: 'rang',
522
+ pastPart: 'rung',
523
+ presPart: 'ringing',
524
+ },
525
+ swing: {
526
+ '3sg': 'swings',
527
+ past: 'swung',
528
+ pastPart: 'swung',
529
+ presPart: 'swinging',
530
+ },
531
+ blow: {
532
+ '3sg': 'blows',
533
+ past: 'blew',
534
+ pastPart: 'blown',
535
+ presPart: 'blowing',
536
+ },
537
+ show: {
538
+ '3sg': 'shows',
539
+ past: 'showed',
540
+ pastPart: 'shown',
541
+ presPart: 'showing',
542
+ },
543
+ shut: {
544
+ '3sg': 'shuts',
545
+ past: 'shut',
546
+ pastPart: 'shut',
547
+ presPart: 'shutting',
548
+ },
549
+ cut: {
550
+ '3sg': 'cuts',
551
+ past: 'cut',
552
+ pastPart: 'cut',
553
+ presPart: 'cutting',
554
+ },
555
+ hit: {
556
+ '3sg': 'hits',
557
+ past: 'hit',
558
+ pastPart: 'hit',
559
+ presPart: 'hitting',
560
+ },
561
+ hurt: {
562
+ '3sg': 'hurts',
563
+ past: 'hurt',
564
+ pastPart: 'hurt',
565
+ presPart: 'hurting',
566
+ },
567
+ let: {
568
+ '3sg': 'lets',
569
+ past: 'let',
570
+ pastPart: 'let',
571
+ presPart: 'letting',
572
+ },
573
+ set: {
574
+ '3sg': 'sets',
575
+ past: 'set',
576
+ pastPart: 'set',
577
+ presPart: 'setting',
578
+ },
579
+ cost: {
580
+ '3sg': 'costs',
581
+ past: 'cost',
582
+ pastPart: 'cost',
583
+ presPart: 'costing',
584
+ },
585
+ send: {
586
+ '3sg': 'sends',
587
+ past: 'sent',
588
+ pastPart: 'sent',
589
+ presPart: 'sending',
590
+ },
591
+ build: {
592
+ '3sg': 'builds',
593
+ past: 'built',
594
+ pastPart: 'built',
595
+ presPart: 'building',
596
+ },
597
+ spend: {
598
+ '3sg': 'spends',
599
+ past: 'spent',
600
+ pastPart: 'spent',
601
+ presPart: 'spending',
602
+ },
603
+ lend: {
604
+ '3sg': 'lends',
605
+ past: 'lent',
606
+ pastPart: 'lent',
607
+ presPart: 'lending',
608
+ },
609
+ lose: {
610
+ '3sg': 'loses',
611
+ past: 'lost',
612
+ pastPart: 'lost',
613
+ presPart: 'losing',
614
+ },
615
+ mean: {
616
+ '3sg': 'means',
617
+ past: 'meant',
618
+ pastPart: 'meant',
619
+ presPart: 'meaning',
620
+ },
621
+ meet: {
622
+ '3sg': 'meets',
623
+ past: 'met',
624
+ pastPart: 'met',
625
+ presPart: 'meeting',
626
+ },
627
+ pay: {
628
+ '3sg': 'pays',
629
+ past: 'paid',
630
+ pastPart: 'paid',
631
+ presPart: 'paying',
632
+ },
633
+ sell: {
634
+ '3sg': 'sells',
635
+ past: 'sold',
636
+ pastPart: 'sold',
637
+ presPart: 'selling',
638
+ },
639
+ hang: {
640
+ '3sg': 'hangs',
641
+ past: 'hung',
642
+ pastPart: 'hung',
643
+ presPart: 'hanging',
644
+ },
645
+ shine: {
646
+ '3sg': 'shines',
647
+ past: 'shone',
648
+ pastPart: 'shone',
649
+ presPart: 'shining',
650
+ },
651
+ dig: {
652
+ '3sg': 'digs',
653
+ past: 'dug',
654
+ pastPart: 'dug',
655
+ presPart: 'digging',
656
+ },
657
+ stick: {
658
+ '3sg': 'sticks',
659
+ past: 'stuck',
660
+ pastPart: 'stuck',
661
+ presPart: 'sticking',
662
+ },
663
+ spin: {
664
+ '3sg': 'spins',
665
+ past: 'spun',
666
+ pastPart: 'spun',
667
+ presPart: 'spinning',
668
+ },
669
+ spread: {
670
+ '3sg': 'spreads',
671
+ past: 'spread',
672
+ pastPart: 'spread',
673
+ presPart: 'spreading',
674
+ },
675
+ bite: {
676
+ '3sg': 'bites',
677
+ past: 'bit',
678
+ pastPart: 'bitten',
679
+ presPart: 'biting',
680
+ },
681
+ feed: {
682
+ '3sg': 'feeds',
683
+ past: 'fed',
684
+ pastPart: 'fed',
685
+ presPart: 'feeding',
686
+ },
687
+ lead: {
688
+ '3sg': 'leads',
689
+ past: 'led',
690
+ pastPart: 'led',
691
+ presPart: 'leading',
692
+ },
693
+ light: {
694
+ '3sg': 'lights',
695
+ past: 'lit',
696
+ pastPart: 'lit',
697
+ presPart: 'lighting',
698
+ },
699
+ shoot: {
700
+ '3sg': 'shoots',
701
+ past: 'shot',
702
+ pastPart: 'shot',
703
+ presPart: 'shooting',
704
+ },
705
+ slide: {
706
+ '3sg': 'slides',
707
+ past: 'slid',
708
+ pastPart: 'slid',
709
+ presPart: 'sliding',
710
+ },
711
+ },
712
+ Noun: {
713
+ child: { plural: 'children' },
714
+ person: { plural: 'people' },
715
+ man: { plural: 'men' },
716
+ woman: { plural: 'women' },
717
+ mouse: { plural: 'mice' },
718
+ foot: { plural: 'feet' },
719
+ tooth: { plural: 'teeth' },
720
+ goose: { plural: 'geese' },
721
+ sheep: { plural: 'sheep' },
722
+ fish: { plural: 'fish' },
723
+ deer: { plural: 'deer' },
724
+ ox: { plural: 'oxen' },
725
+ leaf: { plural: 'leaves' },
726
+ loaf: { plural: 'loaves' },
727
+ wolf: { plural: 'wolves' },
728
+ calf: { plural: 'calves' },
729
+ half: { plural: 'halves' },
730
+ knife: { plural: 'knives' },
731
+ life: { plural: 'lives' },
732
+ wife: { plural: 'wives' },
733
+ self: { plural: 'selves' },
734
+ shelf: { plural: 'shelves' },
735
+ elf: { plural: 'elves' },
736
+ thief: { plural: 'thieves' },
737
+ roof: { plural: 'roofs' },
738
+ chief: { plural: 'chiefs' },
739
+ belief: { plural: 'beliefs' },
740
+ proof: { plural: 'proofs' },
741
+ hoof: { plural: 'hooves' },
742
+ scarf: { plural: 'scarves' },
743
+ wharf: { plural: 'wharves' },
744
+ bus: { plural: 'buses' },
745
+ glass: { plural: 'glasses' },
746
+ class: { plural: 'classes' },
747
+ box: { plural: 'boxes' },
748
+ fox: { plural: 'foxes' },
749
+ watch: { plural: 'watches' },
750
+ match: { plural: 'matches' },
751
+ brush: { plural: 'brushes' },
752
+ dish: { plural: 'dishes' },
753
+ wish: { plural: 'wishes' },
754
+ wash: { plural: 'washes' },
755
+ bush: { plural: 'bushes' },
756
+ push: { plural: 'pushes' },
757
+ potato: { plural: 'potatoes' },
758
+ tomato: { plural: 'tomatoes' },
759
+ hero: { plural: 'heroes' },
760
+ echo: { plural: 'echoes' },
761
+ veto: { plural: 'vetoes' },
762
+ mango: { plural: 'mangoes' },
763
+ mosquito: { plural: 'mosquitoes' },
764
+ tornado: { plural: 'tornadoes' },
765
+ volcano: { plural: 'volcanoes' },
766
+ radio: { plural: 'radios' },
767
+ studio: { plural: 'studios' },
768
+ video: { plural: 'videos' },
769
+ piano: { plural: 'pianos' },
770
+ photo: { plural: 'photos' },
771
+ zoo: { plural: 'zoos' },
772
+ bamboo: { plural: 'bamboos' },
773
+ embryo: { plural: 'embryos' },
774
+ ratio: { plural: 'ratios' },
775
+ scenario: { plural: 'scenarios' },
776
+ analysis: { plural: 'analyses' },
777
+ basis: { plural: 'bases' },
778
+ crisis: { plural: 'crises' },
779
+ diagnosis: { plural: 'diagnoses' },
780
+ hypothesis: { plural: 'hypotheses' },
781
+ oasis: { plural: 'oases' },
782
+ parenthesis: { plural: 'parentheses' },
783
+ synthesis: { plural: 'syntheses' },
784
+ thesis: { plural: 'theses' },
785
+ phenomenon: { plural: 'phenomena' },
786
+ criterion: { plural: 'criteria' },
787
+ datum: { plural: 'data' },
788
+ medium: { plural: 'media' },
789
+ curriculum: { plural: 'curricula' },
790
+ bacterium: { plural: 'bacteria' },
791
+ stimulus: { plural: 'stimuli' },
792
+ syllabus: { plural: 'syllabi' },
793
+ focus: { plural: 'foci' },
794
+ nucleus: { plural: 'nuclei' },
795
+ fungus: { plural: 'fungi' },
796
+ cactus: { plural: 'cacti' },
797
+ appendix: { plural: 'appendices' },
798
+ index: { plural: 'indices' },
799
+ matrix: { plural: 'matrices' },
800
+ vertex: { plural: 'vertices' },
801
+ },
802
+ Adjective: {
803
+ good: { comparative: 'better', superlative: 'best' },
804
+ bad: { comparative: 'worse', superlative: 'worst' },
805
+ far: { comparative: 'farther', superlative: 'farthest' },
806
+ little: { comparative: 'less', superlative: 'least' },
807
+ much: { comparative: 'more', superlative: 'most' },
808
+ many: { comparative: 'more', superlative: 'most' },
809
+ well: { comparative: 'better', superlative: 'best' },
810
+ old: {
811
+ comparative: 'older',
812
+ superlative: 'oldest',
813
+ extra: ['elder', 'eldest'],
814
+ },
815
+ late: {
816
+ comparative: 'later',
817
+ superlative: 'latest',
818
+ extra: ['latter', 'last'],
819
+ },
820
+ },
821
+ Pronoun: {
822
+ i: {
823
+ objective: 'me',
824
+ possessive: 'my',
825
+ possessivePronoun: 'mine',
826
+ },
827
+ you: {
828
+ objective: 'you',
829
+ possessive: 'your',
830
+ possessivePronoun: 'yours',
831
+ },
832
+ he: {
833
+ objective: 'him',
834
+ possessive: 'his',
835
+ possessivePronoun: 'his',
836
+ },
837
+ she: {
838
+ objective: 'her',
839
+ possessive: 'her',
840
+ possessivePronoun: 'hers',
841
+ },
842
+ it: {
843
+ objective: 'it',
844
+ possessive: 'its',
845
+ },
846
+ we: {
847
+ objective: 'us',
848
+ possessive: 'our',
849
+ possessivePronoun: 'ours',
850
+ },
851
+ they: {
852
+ objective: 'them',
853
+ possessive: 'their',
854
+ possessivePronoun: 'theirs',
855
+ },
856
+ mine: { extra: ['my'] },
857
+ yours: { extra: ['your'] },
858
+ his: { extra: ['him'] },
859
+ hers: { extra: ['her'] },
860
+ ours: { extra: ['our'] },
861
+ theirs: { extra: ['their'] },
862
+ },
863
+ },
864
+ regular: {
865
+ Verb: {
866
+ '3sg': [
867
+ { match: '(ss|sh|ch|x|z|o)$', replace: '$1es' },
868
+ { match: '([^aeiou])y$', replace: '$1ies' },
869
+ { match: '$', replace: 's' },
870
+ ],
871
+ past: [
872
+ { match: '([^aeiou])y$', replace: '$1ied' },
873
+ { match: '([^aeiou][aeiou])([^aeiouwxy])$', replace: '$1$2$2ed' },
874
+ { match: '(.*)e$', replace: '$1ed' },
875
+ { match: '$', replace: 'ed' },
876
+ ],
877
+ pastPart: 'past',
878
+ presPart: [
879
+ { match: 'ie$', replace: 'ying' },
880
+ { match: '(.*)e$', replace: '$1ing' },
881
+ { match: '([^aeiou][aeiou])([^aeiouwxy])$', replace: '$1$2$2ing' },
882
+ { match: '$', replace: 'ing' },
883
+ ],
884
+ },
885
+ Noun: {
886
+ plural: [
887
+ { match: '(ss|sh|ch|x|z)$', replace: '$1es' },
888
+ { match: '([^aeiou])y$', replace: '$1ies' },
889
+ { match: 'fe$', replace: 'ves' },
890
+ { match: 'f$', replace: 'ves' },
891
+ { match: '$', replace: 's' },
892
+ ],
893
+ },
894
+ Adjective: {
895
+ comparative: [
896
+ { match: 'e$', replace: 'r' },
897
+ { match: '([^aeiou])y$', replace: '$1ier' },
898
+ { match: '([^aeiou][aeiou])([^aeiouwxy])$', replace: '$1$2$2er' },
899
+ { match: '$', replace: 'er' },
900
+ ],
901
+ superlative: [
902
+ { match: 'e$', replace: 'st' },
903
+ { match: '([^aeiou])y$', replace: '$1iest' },
904
+ { match: '([^aeiou][aeiou])([^aeiouwxy])$', replace: '$1$2$2est' },
905
+ { match: '$', replace: 'est' },
906
+ ],
907
+ },
908
+ },
909
+ };
910
+ }