File Coverage

blib/lib/Lingua/ENG/Inflect.pm
Criterion Covered Total %
statement 339 399 84.9
branch 360 440 81.8
condition 117 252 46.4
subroutine 36 48 75.0
pod 9 33 27.2
total 861 1172 73.4


line stmt bran cond sub pod time code
1             package Lingua::ENG::Inflect;
2             # ABSTRACT: Plural inflection for ENG.
3              
4 14     14   16021 use 5.10.1;
  14         47  
  14         690  
5 14     14   65 use strict;
  14         21  
  14         483  
6 14     14   65 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS @ISA);
  14         32  
  14         1182  
7 14     14   26765 use Env;
  14         59032  
  14         97  
8              
9             require Exporter;
10             @ISA = qw(Exporter);
11              
12             our $VERSION = 0.0682;
13              
14             %EXPORT_TAGS =
15             (
16             ALL => [ qw( classical inflect
17             PL PL_N PL_V PL_ADJ NO NUM A AN
18             PL_eq PL_N_eq PL_V_eq PL_ADJ_eq
19             PART_PRES
20             ORD
21             NUMWORDS
22             WORDLIST
23             def_noun def_verb def_adj def_a def_an )],
24              
25             INFLECTIONS => [ qw( classical inflect
26             PL PL_N PL_V PL_ADJ PL_eq
27             NO NUM A AN PART_PRES )],
28              
29             PLURALS => [ qw( classical inflect
30             PL PL_N PL_V PL_ADJ NO NUM
31             PL_eq PL_N_eq PL_V_eq PL_ADJ_eq )],
32              
33             COMPARISONS => [ qw( classical
34             PL_eq PL_N_eq PL_V_eq PL_ADJ_eq )],
35              
36             ARTICLES => [ qw( classical inflect NUM A AN )],
37              
38             NUMERICAL => [ qw( ORD NUMWORDS )],
39              
40             USER_DEFINED => [ qw( def_noun def_verb def_adj def_a def_an )],
41             );
42              
43             Exporter::export_ok_tags(qw( ALL ));
44              
45             # SUPPORT CLASSICAL PLURALIZATIONS
46              
47             my %def_classical = (
48             all => 0,
49             zero => 0,
50             herd => 0,
51             names => 1,
52             persons => 0,
53             ancient => 0,
54             );
55              
56             my %all_classical = (
57             all => 1,
58             zero => 1,
59             herd => 1,
60             names => 1,
61             persons => 1,
62             ancient => 1,
63             );
64              
65             my %classical = %def_classical;
66              
67             my $classical_mode = join '|', keys %all_classical;
68             $classical_mode = qr/^(?:$classical_mode)$/;
69              
70             sub classical
71             {
72 1712 100   1712 0 292418 if (!@_) {
73 1         6 %classical = %all_classical;
74 1         2 return;
75             }
76 1711 100 100     6651 if (@_==1 && $_[0] !~ $classical_mode) {
77 5 100       31 %classical = $_[0] ? %all_classical : ();
78 5         12 return;
79             }
80 1706         3763 while (@_) {
81 2552         3207 my $arg = shift;
82 2552 50       14902 if ($arg !~ $classical_mode) {
83 0         0 die "Unknown classical mode ($arg)\n";
84             }
85 2552 100 66     13997 if (@_ && $_[0] !~ $classical_mode) { $classical{$arg} = shift; }
  2546         5736  
86 6         14 else { $classical{$arg} = 1; }
87              
88 2552 100       7683 if ($arg eq 'all') {
89 1695 100       12172 %classical = $classical{all} ? %all_classical : ();
90             }
91             }
92             }
93              
94             my $persistent_count;
95              
96             sub NUM # (;$count,$show)
97             {
98 0 0   0 0 0 if (defined $_[0])
99             {
100 0         0 $persistent_count = $_[0];
101 0 0 0     0 return $_[0] if !defined($_[1]) || $_[1];
102             }
103             else
104             {
105 0         0 $persistent_count = undef;
106             }
107 0         0 return '';
108             }
109              
110              
111             # 0. PERFORM GENERAL INFLECTIONS IN A STRING
112              
113 602     602 0 1494 sub enclose { "(?:$_[0])" }
114              
115             sub inflect
116             {
117 0     0 0 0 my $save_persistent_count = $persistent_count;
118 0         0 my @sections = split /(NUM\([^)]*\))/, $_[0];
119 0         0 my $inflection = "";
120              
121 0         0 foreach ( @sections )
122             {
123 0 0       0 unless (s/NUM\(\s*?(?:([^),]*)(?:,([^)]*))?)?\)/ NUM($1,$2) /xe)
  0         0  
124             {
125 0   0     0 1 while
      0        
      0        
      0        
      0        
      0        
      0        
      0        
126 0         0 s/\bPL \( ([^),]*) (, ([^)]*) )? \) / PL($1,$3) /xeg
127 0         0 || s/\bPL_N \( ([^),]*) (, ([^)]*) )? \) / PL_N($1,$3) /xeg
128 0         0 || s/\bPL_V \( ([^),]*) (, ([^)]*) )? \) / PL_V($1,$3) /xeg
129 0         0 || s/\bPL_ADJ \( ([^),]*) (, ([^)]*) )? \) / PL_ADJ($1,$3) /xeg
130 0         0 || s/\bAN? \( ([^),]*) (, ([^)]*) )? \) / A($1,$3) /xeg
131 0         0 || s/\bNO \( ([^),]*) (, ([^)]*) )? \) / NO($1,$3) /xeg
132 0         0 || s/\bORD \( ([^)]*) \) / ORD($1) /xeg
133 0         0 || s/\bNUMWORDS \( ([^)]*) \) / NUMWORDS($1) /xeg
134 0         0 || s/\bPART_PRES \( ([^)]*) \) / PART_PRES($1) /xeg
135             }
136              
137 0         0 $inflection .= $_;
138             }
139              
140 0         0 $persistent_count = $save_persistent_count;
141 0         0 return $inflection;
142             }
143              
144              
145             # 1. PLURALS
146              
147             my %PL_sb_irregular_s =
148             (
149             "corpus" => "corpuses|corpora",
150             "opus" => "opuses|opera",
151             "genus" => "genera",
152             "mythos" => "mythoi",
153             "penis" => "penises|penes",
154             "testis" => "testes",
155             "atlas" => "atlases|atlantes",
156             "yes" => "yeses",
157             );
158              
159             my %PL_sb_irregular =
160             (
161             "child" => "children",
162             "brother" => "brothers|brethren",
163             "loaf" => "loaves",
164             "hoof" => "hoofs|hooves",
165             "beef" => "beefs|beeves",
166             "thief" => "thiefs|thieves",
167             "money" => "monies",
168             "mongoose" => "mongooses",
169             "ox" => "oxen",
170             "cow" => "cows|kine",
171             "graffito" => "graffiti",
172             "prima donna" => "prima donnas|prime donne",
173             "octopus" => "octopuses|octopodes",
174             "genie" => "genies|genii",
175             "ganglion" => "ganglions|ganglia",
176             "trilby" => "trilbys",
177             "turf" => "turfs|turves",
178             "numen" => "numina",
179             "atman" => "atmas",
180             "occiput" => "occiputs|occipita",
181             'sabretooth' => 'sabretooths',
182             'sabertooth' => 'sabertooths',
183             'lowlife' => 'lowlifes',
184             'flatfoot' => 'flatfoots',
185             'tenderfoot' => 'tenderfoots',
186             'Romany' => 'Romanies',
187             'romany' => 'romanies',
188             'Jerry' => 'Jerrys',
189             'jerry' => 'jerries',
190             'Mary' => 'Marys',
191             'mary' => 'maries',
192             'talouse' => 'talouses',
193             'blouse' => 'blouses',
194             'Rom' => 'Roma',
195             'rom' => 'roma',
196              
197             %PL_sb_irregular_s,
198             );
199              
200             my $PL_sb_irregular = enclose join '|', keys %PL_sb_irregular;
201              
202             # CLASSICAL "..is" -> "..ides"
203              
204             my @PL_sb_C_is_ides =
205             (
206             # GENERAL WORDS...
207              
208             "ephemeris", "iris", "clitoris",
209             "chrysalis", "epididymis",
210              
211             # INFLAMATIONS...
212              
213             ".*itis",
214              
215             );
216              
217             my $PL_sb_C_is_ides = enclose join "|", map { substr($_,0,-2) } @PL_sb_C_is_ides;
218              
219             # CLASSICAL "..a" -> "..ata"
220              
221             my @PL_sb_C_a_ata =
222             (
223             "anathema", "bema", "carcinoma", "charisma", "diploma",
224             "dogma", "drama", "edema", "enema", "enigma", "lemma",
225             "lymphoma", "magma", "melisma", "miasma", "oedema",
226             "sarcoma", "schema", "soma", "stigma", "stoma", "trauma",
227             "gumma", "pragma",
228             );
229              
230             my $PL_sb_C_a_ata = enclose join "|", map { substr($_,0,-1) } @PL_sb_C_a_ata;
231              
232             # UNCONDITIONAL "..a" -> "..ae"
233              
234             my $PL_sb_U_a_ae = enclose join "|",
235             (
236             "alumna", "alga", "vertebra", "persona"
237             );
238              
239             # CLASSICAL "..a" -> "..ae"
240              
241             my $PL_sb_C_a_ae = enclose join "|",
242             (
243             "amoeba", "antenna", "formula", "hyperbola",
244             "medusa", "nebula", "parabola", "abscissa",
245             "hydra", "nova", "lacuna", "aurora", ".*umbra",
246             "flora", "fauna",
247             );
248              
249             # CLASSICAL "..en" -> "..ina"
250              
251             my $PL_sb_C_en_ina = enclose join "|", map { substr($_,0,-2) }
252             (
253             "stamen", "foramen", "lumen", "carmen"
254             );
255              
256             # UNCONDITIONAL "..um" -> "..a"
257              
258             my $PL_sb_U_um_a = enclose join "|", map { substr($_,0,-2) }
259             (
260             "bacterium", "agendum", "desideratum", "erratum",
261             "stratum", "datum", "ovum", "extremum",
262             "candelabrum",
263             );
264              
265             # CLASSICAL "..um" -> "..a"
266              
267             my $PL_sb_C_um_a = enclose join "|", map { substr($_,0,-2) }
268             (
269             "maximum", "minimum", "momentum", "optimum",
270             "quantum", "cranium", "curriculum", "dictum",
271             "phylum", "aquarium", "compendium", "emporium",
272             "enconium", "gymnasium", "honorarium", "interregnum",
273             "lustrum", "memorandum", "millennium", "rostrum",
274             "spectrum", "speculum", "stadium", "trapezium",
275             "ultimatum", "medium", "vacuum", "velum",
276             "consortium",
277             );
278              
279             # UNCONDITIONAL "..us" -> "i"
280              
281             my $PL_sb_U_us_i = enclose join "|", map { substr($_,0,-2) }
282             (
283             "alumnus", "alveolus", "bacillus", "bronchus",
284             "locus", "nucleus", "stimulus", "meniscus",
285             "sarcophagus",
286             );
287              
288             # CLASSICAL "..us" -> "..i"
289              
290             my $PL_sb_C_us_i = enclose join "|", map { substr($_,0,-2) }
291             (
292             "focus", "radius", "genius",
293             "incubus", "succubus", "nimbus",
294             "fungus", "nucleolus", "stylus",
295             "torus", "umbilicus", "uterus",
296             "hippopotamus", "cactus",
297             );
298              
299             # CLASSICAL "..us" -> "..us" (ASSIMILATED 4TH DECLENSION LATIN NOUNS)
300              
301             my $PL_sb_C_us_us = enclose join "|",
302             (
303             "status", "apparatus", "prospectus", "sinus",
304             "hiatus", "impetus", "plexus",
305             );
306              
307             # UNCONDITIONAL "..on" -> "a"
308              
309             my $PL_sb_U_on_a = enclose join "|", map { substr($_,0,-2) }
310             (
311             "criterion", "perihelion", "aphelion",
312             "phenomenon", "prolegomenon", "noumenon",
313             "organon", "asyndeton", "hyperbaton",
314             );
315              
316             # CLASSICAL "..on" -> "..a"
317              
318             my $PL_sb_C_on_a = enclose join "|", map { substr($_,0,-2) }
319             (
320             "oxymoron",
321             );
322              
323             # CLASSICAL "..o" -> "..i" (BUT NORMALLY -> "..os")
324              
325             my @PL_sb_C_o_i =
326             (
327             "solo", "soprano", "basso", "alto",
328             "contralto", "tempo", "piano", "virtuoso",
329             );
330             my $PL_sb_C_o_i = enclose join "|", map { substr($_,0,-1) } @PL_sb_C_o_i;
331              
332             # ALWAYS "..o" -> "..os"
333              
334             my $PL_sb_U_o_os = enclose join "|",
335             (
336             "^ado", "aficionado", "aggro",
337             "albino", "allegro", "ammo",
338             "Antananarivo", "archipelago", "armadillo",
339             "auto", "avocado", "Bamako",
340             "Barquisimeto", "bimbo", "bingo",
341             "Biro", "bolero", "Bolzano",
342             "bongo", "Boto", "burro",
343             "Cairo", "canto", "cappuccino",
344             "casino", "cello", "Chicago",
345             "Chimango", "cilantro", "cochito",
346             "coco", "Colombo", "Colorado",
347             "commando", "concertino", "contango",
348             "credo", "crescendo", "cyano",
349             "demo", "ditto", "Draco",
350             "dynamo", "embryo", "Esperanto",
351             "espresso", "euro", "falsetto",
352             "Faro", "fiasco", "Filipino",
353             "flamenco", "furioso", "generalissimo",
354             "Gestapo", "ghetto", "gigolo",
355             "gizmo", "Greensboro", "gringo",
356             "Guaiabero", "guano", "gumbo",
357             "gyro", "hairdo", "hippo",
358             "Idaho", "impetigo", "inferno",
359             "info", "intermezzo", "intertrigo",
360             "Iquico", "^ISO", "jumbo",
361             "junto", "Kakapo", "kilo",
362             "Kinkimavo", "Kokako", "Kosovo",
363             "Lesotho", "libero", "libido",
364             "libretto", "lido", "Lilo",
365             "limbo", "limo", "lineno",
366             "lingo", "lino", "livedo",
367             "loco", "logo", "lumbago",
368             "macho", "macro", "mafioso",
369             "magneto", "magnifico", "Majuro",
370             "Malabo", "manifesto", "Maputo",
371             "Maracaibo", "medico", "memo",
372             "metro", "Mexico", "micro",
373             "Milano", "Monaco", "mono",
374             "Montenegro", "Morocco", "Muqdisho",
375             "myo", "^NATO", "^NCO",
376             "neutrino", "^NGO", "Ningbo",
377             "octavo", "oregano", "Orinoco",
378             "Orlando", "Oslo", "^oto",
379             "panto", "Paramaribo", "Pardusco",
380             "pedalo", "photo", "pimento",
381             "pinto", "pleco", "Pluto",
382             "pogo", "polo", "poncho",
383             "Porto-Novo", "Porto", "pro",
384             "psycho", "pueblo", "quarto",
385             "Quito", "rhino", "risotto",
386             "rococo", "rondo", "Sacramento",
387             "saddo", "sago", "salvo",
388             "Santiago", "Sapporo", "Sarajevo",
389             "scherzando", "scherzo", "silo",
390             "sirocco", "sombrero", "staccato",
391             "sterno", "stucco", "stylo",
392             "sumo", "Taiko", "techno",
393             "terrazzo", "testudo", "timpano",
394             "tiro", "tobacco", "Togo",
395             "Tokyo", "torero", "Torino",
396             "Toronto", "torso", "tremolo",
397             "typo", "tyro", "ufo",
398             "UNESCO", "vaquero", "vermicello",
399             "verso", "vibrato", "violoncello",
400             "Virgo", "weirdo", "WHO",
401             "WTO", "Yamoussoukro", "yo-yo",
402             "zero", "Zibo",
403              
404             @PL_sb_C_o_i,
405             );
406              
407              
408             # UNCONDITIONAL "..ch" -> "..chs"
409              
410             my $PL_sb_U_ch_chs = enclose join "|", map { substr($_,0,-2) }
411             qw(
412             czech eunuch stomach
413             );
414              
415             # UNCONDITIONAL "..[ei]x" -> "..ices"
416              
417             my $PL_sb_U_ex_ices = enclose join "|", map { substr($_,0,-2) }
418             (
419             "codex", "murex", "silex",
420             );
421              
422             my $PL_sb_U_ix_ices = enclose join "|", map { substr($_,0,-2) }
423             (
424             "radix", "helix",
425             );
426              
427             # CLASSICAL "..[ei]x" -> "..ices"
428              
429             my $PL_sb_C_ex_ices = enclose join "|", map { substr($_,0,-2) }
430             (
431             "vortex", "vertex", "cortex", "latex",
432             "pontifex", "apex", "index", "simplex",
433             );
434              
435             my $PL_sb_C_ix_ices = enclose join "|", map { substr($_,0,-2) }
436             (
437             "appendix",
438             );
439              
440             # ARABIC: ".." -> "..i"
441              
442             my $PL_sb_C_i = enclose join "|",
443             (
444             "afrit", "afreet", "efreet",
445             );
446              
447             # HEBREW: ".." -> "..im"
448              
449             my $PL_sb_C_im = enclose join "|",
450             (
451             "goy", "seraph", "cherub",
452             );
453              
454             # UNCONDITIONAL "..man" -> "..mans"
455              
456             my $PL_sb_U_man_mans = enclose join "|",
457             qw(
458             ataman caiman cayman ceriman
459             desman dolman farman harman hetman
460             human leman ottoman shaman talisman
461             Alabaman Bahaman Burman German
462             Hiroshiman Liman Nakayaman Norman Oklahoman
463             Panaman Roman Selman Sonaman Tacoman Yakiman
464             Yokohaman Yuman
465             );
466              
467             my @PL_sb_uninflected_s =
468             (
469             # PAIRS OR GROUPS SUBSUMED TO A SINGULAR...
470             "breeches", "britches", "pajamas", "pyjamas", "clippers", "gallows",
471             "hijinks", "headquarters", "pliers", "scissors", "testes", "herpes",
472             "pincers", "shears", "proceedings", "trousers",
473              
474             # UNASSIMILATED LATIN 4th DECLENSION
475              
476             "cantus", "coitus", "nexus",
477              
478             # RECENT IMPORTS...
479             "contretemps", "corps", "debris",
480             ".*ois", "siemens",
481            
482             # DISEASES
483             ".*measles", "mumps",
484              
485             # MISCELLANEOUS OTHERS...
486             "diabetes", "jackanapes", "series", "species", "rabies",
487             "chassis", "innings", "news", "mews", "haggis",
488             );
489              
490             my $PL_sb_uninflected_herd = enclose join "|",
491             # DON'T INFLECT IN CLASSICAL MODE, OTHERWISE NORMAL INFLECTION
492             (
493             "wildebeest", "swine", "eland", "bison", "buffalo",
494             "elk", "rhinoceros", 'zucchini',
495             'caribou', 'dace', 'grouse', 'guinea[- ]fowl',
496             'haddock', 'hake', 'halibut', 'herring', 'mackerel',
497             'pickerel', 'pike', 'roe', 'seed', 'shad',
498             'snipe', 'teal', 'turbot', 'water[- ]fowl',
499             );
500              
501             my $PL_sb_uninflected = enclose join "|",
502             (
503             # SOME FISH AND HERD ANIMALS
504             ".*fish", "tuna", "salmon", "mackerel", "trout",
505             "bream", "sea[- ]bass", "carp", "cod", "flounder", "whiting",
506              
507             ".*deer", ".*sheep", "moose",
508              
509             # ALL NATIONALS ENDING IN -ese
510             "Portuguese", "Amoyese", "Borghese", "Congoese", "Faroese",
511             "Foochowese", "Genevese", "Genoese", "Gilbertese", "Hottentotese",
512             "Kiplingese", "Kongoese", "Lucchese", "Maltese", "Nankingese",
513             "Niasese", "Pekingese", "Piedmontese", "Pistoiese", "Sarawakese",
514             "Shavese", "Vermontese", "Wenchowese", "Yengeese",
515             ".*[nrlm]ese",
516              
517             # SOME WORDS ENDING IN ...s (OFTEN PAIRS TAKEN AS A WHOLE)
518              
519             @PL_sb_uninflected_s,
520              
521             # DISEASES
522             ".*pox",
523              
524              
525             # OTHER ODDITIES
526             "graffiti", "djinn", 'samuri',
527             '.*craft$', 'offspring', 'pence', 'quid', 'hertz',
528             );
529              
530             # SINGULAR WORDS ENDING IN ...s (ALL INFLECT WITH ...es)
531              
532             my $PL_sb_singular_s = enclose join '|',
533             (
534             ".*ss",
535             "acropolis", "aegis", "alias", "asbestos", "bathos", "bias",
536             "bronchitis", "bursitis", "caddis", "cannabis",
537             "canvas", "chaos", "cosmos", "dais", "digitalis",
538             "epidermis", "ethos", "eyas", "gas", "glottis",
539             "hubris", "ibis", "lens", "mantis", "marquis", "metropolis",
540             "pathos", "pelvis", "polis", "rhinoceros",
541             "sassafras", "trellis", ".*us", "[A-Z].*es",
542            
543             @PL_sb_C_is_ides,
544             );
545              
546             my $PL_v_special_s = enclose join '|',
547             (
548             $PL_sb_singular_s,
549             @PL_sb_uninflected_s,
550             keys %PL_sb_irregular_s,
551             '(.*[csx])is',
552             '(.*)ceps',
553             '[A-Z].*s',
554             );
555              
556             my %PL_sb_postfix_adj = (
557             'general' => ['(?!major|lieutenant|brigadier|adjutant)\S+'],
558             'martial' => [qw(court)],
559             );
560              
561             foreach (keys %PL_sb_postfix_adj) {
562             $PL_sb_postfix_adj{$_} = enclose
563             enclose(join('|', @{$PL_sb_postfix_adj{$_}}))
564             . "(?=(?:-|\\s+)$_)";
565             }
566              
567             my $PL_sb_postfix_adj = '(' . join('|', values %PL_sb_postfix_adj) . ')(.*)';
568              
569             my $PL_sb_military = 'major|lieutenant|brigadier|adjutant|quartermaster';
570             my $PL_sb_general = '((?!'.$PL_sb_military.').*?)((-|\s+)general)';
571              
572             my $PL_prep = enclose join '|', qw (
573             about above across after among around at athwart before behind
574             below beneath beside besides between betwixt beyond but by
575             during except for from in into near of off on onto out over
576             since till to under until unto upon with
577             );
578              
579             my $PL_sb_prep_dual_compound = '(.*?)((?:-|\s+)(?:'.$PL_prep.'|d[eua])(?:-|\s+))a(?:-|\s+)(.*)';
580              
581             my $PL_sb_prep_compound = '(.*?)((-|\s+)('.$PL_prep.'|d[eua])((-|\s+)(.*))?)';
582              
583              
584             my %PL_pron_nom =
585             (
586             # NOMINATIVE REFLEXIVE
587              
588             "i" => "we", "myself" => "ourselves",
589             "you" => "you", "yourself" => "yourselves",
590             "she" => "they", "herself" => "themselves",
591             "he" => "they", "himself" => "themselves",
592             "it" => "they", "itself" => "themselves",
593             "they" => "they", "themself" => "themselves",
594              
595             # POSSESSIVE
596              
597             "mine" => "ours",
598             "yours" => "yours",
599             "hers" => "theirs",
600             "his" => "theirs",
601             "its" => "theirs",
602             "theirs" => "theirs",
603             );
604              
605             my %PL_pron_acc =
606             (
607             # ACCUSATIVE REFLEXIVE
608              
609             "me" => "us", "myself" => "ourselves",
610             "you" => "you", "yourself" => "yourselves",
611             "her" => "them", "herself" => "themselves",
612             "him" => "them", "himself" => "themselves",
613             "it" => "them", "itself" => "themselves",
614             "them" => "them", "themself" => "themselves",
615             );
616              
617             my $PL_pron_acc = enclose join '|', keys %PL_pron_acc;
618              
619             my %PL_v_irregular_pres =
620             (
621             # 1st PERS. SING. 2ND PERS. SING. 3RD PERS. SINGULAR
622             # 3RD PERS. (INDET.)
623              
624             "am" => "are", "are" => "are", "is" => "are",
625             "was" => "were", "were" => "were", "was" => "were",
626             "have" => "have", "have" => "have", "has" => "have",
627             "do" => "do", "do" => "do", "does" => "do",
628             );
629              
630             my $PL_v_irregular_pres = enclose join '|', keys %PL_v_irregular_pres;
631              
632             my %PL_v_ambiguous_pres =
633             (
634             # 1st PERS. SING. 2ND PERS. SING. 3RD PERS. SINGULAR
635             # 3RD PERS. (INDET.)
636              
637             "act" => "act", "act" => "act", "acts" => "act",
638             "blame" => "blame", "blame" => "blame", "blames" => "blame",
639             "can" => "can", "can" => "can", "can" => "can",
640             "must" => "must", "must" => "must", "must" => "must",
641             "fly" => "fly", "fly" => "fly", "flies" => "fly",
642             "copy" => "copy", "copy" => "copy", "copies" => "copy",
643             "drink" => "drink", "drink" => "drink", "drinks" => "drink",
644             "fight" => "fight", "fight" => "fight", "fights" => "fight",
645             "fire" => "fire", "fire" => "fire", "fires" => "fire",
646             "like" => "like", "like" => "like", "likes" => "like",
647             "look" => "look", "look" => "look", "looks" => "look",
648             "make" => "make", "make" => "make", "makes" => "make",
649             "reach" => "reach", "reach" => "reach", "reaches" => "reach",
650             "run" => "run", "run" => "run", "runs" => "run",
651             "sink" => "sink", "sink" => "sink", "sinks" => "sink",
652             "sleep" => "sleep", "sleep" => "sleep", "sleeps" => "sleep",
653             "view" => "view", "view" => "view", "views" => "view",
654             );
655              
656             my $PL_v_ambiguous_pres = enclose join '|', keys %PL_v_ambiguous_pres;
657              
658              
659             my $PL_v_irregular_non_pres = enclose join '|',
660             (
661             "did", "had", "ate", "made", "put",
662             "spent", "fought", "sank", "gave", "sought",
663             "shall", "could", "ought", "should",
664             );
665              
666             my $PL_v_ambiguous_non_pres = enclose join '|',
667             (
668             "thought", "saw", "bent", "will", "might", "cut",
669             );
670              
671             # "..oes" -> "..oe" (the rest are "..oes" -> "o")
672              
673             my $PL_v_oes_oe = enclose join "|",
674             qw(
675             .*shoes .*hoes .*toes
676             canoes floes oboes roes throes woes
677             );
678              
679             my $PL_count_zero = enclose join '|',
680             (
681             0, "no", "zero", "nil"
682             );
683              
684             my $PL_count_one = enclose join '|',
685             (
686             1, "a", "an", "one", "each", "every", "this", "that",
687             );
688              
689             my %PL_adj_special =
690             (
691             "a" => "some", "an" => "some",
692             "this" => "these", "that" => "those",
693             );
694             my $PL_adj_special = enclose join '|', keys %PL_adj_special;
695              
696             my %PL_adj_poss =
697             (
698             "my" => "our",
699             "your" => "your",
700             "its" => "their",
701             "her" => "their",
702             "his" => "their",
703             "their" => "their",
704             );
705             my $PL_adj_poss = enclose join '|', keys %PL_adj_poss;
706              
707              
708             sub checkpat
709             {
710 0     0 0 0 local $SIG{__WARN__} = sub {0};
  6     6   100  
711 6 50 33     510 do {$@ =~ s/at.*?$//;
  0         0  
712 0         0 die "\nBad user-defined singular pattern:\n\t$@\n"}
713             if (!eval "'' =~ m/$_[0]/; 1;" or $@);
714 6         32 return @_;
715             }
716              
717             sub checkpatsubs
718             {
719 6     6 0 15 checkpat($_[0]);
720 6 50       16 if (defined $_[1])
721             {
722 6     0   39 local $SIG{__WARN__} = sub {0};
  0         0  
723 6 50 33     383 do {$@ =~ s/at.*?$//;
  0         0  
724 0         0 die "\nBad user-defined plural string: '$_[1]'\n\t$@\n"}
725             if (!eval "qq{$_[1]}; 1;" or $@);
726             }
727 6         29 return @_;
728             }
729              
730             my @PL_sb_user_defined = ();
731             my @PL_v_user_defined = ();
732             my @PL_adj_user_defined = ();
733             my @A_a_user_defined = ();
734              
735             sub def_noun
736             {
737 2     2 1 13 unshift @PL_sb_user_defined, checkpatsubs(@_);
738 2         6 return 1;
739             }
740              
741             sub def_verb
742             {
743 1     1 1 216 unshift @PL_v_user_defined, checkpatsubs(@_[4,5]);
744 1         6 unshift @PL_v_user_defined, checkpatsubs(@_[2,3]);
745 1         5 unshift @PL_v_user_defined, checkpatsubs(@_[0,1]);
746 1         4 return 1;
747             }
748              
749             sub def_adj
750             {
751 1     1 1 8 unshift @PL_adj_user_defined, checkpatsubs(@_);
752 1         4 return 1;
753             }
754              
755             sub def_a
756             {
757 0     0 1 0 unshift @A_a_user_defined, checkpat(@_,'a');
758 0         0 return 1;
759             }
760              
761             sub def_an
762             {
763 0     0 1 0 unshift @A_a_user_defined, checkpat(@_,'an');
764 0         0 return 1;
765             }
766              
767             sub ud_match
768             {
769 13744     13744 0 17266 my $word = shift;
770 13744         37823 for (my $i=0; $i < @_; $i+=2)
771             {
772 44 100       771 if ($word =~ /^(?:$_[$i])$/i)
773             {
774 8 50       24 last unless defined $_[$i+1];
775 8         814 return eval '"'.$_[$i+1].'"';
776             }
777             }
778 13736         32401 return;
779             }
780              
781             do
782             {
783             local $SIG{__WARN__} = sub {0};
784             my $rcfile;
785              
786             $rcfile = $INC{'Lingua//ENG/Inflect.pm'} || '';
787             $rcfile =~ s/Inflect.pm$/.inflectrc/;
788             do $rcfile or die "\nBad .inflectrc file ($rcfile):\n\t$@\n"
789             if $rcfile && -r $rcfile && -s $rcfile;
790              
791             $rcfile = "$ENV{HOME}/.inflectrc" || '';
792             do $rcfile or die "\nBad .inflectrc file ($rcfile):\n\t$@\n"
793             if $rcfile && -r $rcfile && -s $rcfile;
794             };
795              
796             sub postprocess # FIX PEDANTRY AND CAPITALIZATION :-)
797             {
798 10285     10285 0 16591 my ($orig, $inflected) = @_;
799 10285 100       14129 $inflected =~ s/([^|]+)\|(.+)/ $classical{all}?$2:$1 /e;
  173         672  
800 10285 100       55191 return $orig =~ /^I$/ ? $inflected
    100          
    100          
801             : $orig =~ /^[A-Z]+$/ ? uc $inflected
802             : $orig =~ /^[A-Z]/ ? ucfirst $inflected
803             : $inflected;
804             }
805              
806             sub PL
807             # PL($word,$number)
808             {
809 1704     1704 1 5474 my ($str, $count) = @_;
810 1704         9128 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
811 1704 50       3586 return $str unless $word;
812 1704   66     3175 my $plural = postprocess $word, _PL_special_adjective($word,$count)
813             || _PL_special_verb($word,$count)
814             || _PL_noun($word,$count);
815 1704         6048 return $pre.$plural.$post;
816             }
817              
818             sub PL_N
819             # PL_N($word,$number)
820             {
821 6553     6553 1 25009 my ($str, $count) = @_;
822 6553         37357 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
823 6553 50       13554 return $str unless $word;
824 6553         12016 my $plural = postprocess $word, _PL_noun($word,$count);
825 6553         31168 return $pre.$plural.$post;
826             }
827              
828             sub PL_V
829             # PL_V($word,$number)
830             {
831 2028     2028 1 6541 my ($str, $count) = @_;
832 2028         11767 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
833 2028 50       4467 return $str unless $word;
834 2028   66     3524 my $plural = postprocess $word, _PL_special_verb($word,$count)
835             || _PL_general_verb($word,$count);
836 2028         7584 return $pre.$plural.$post;
837             }
838              
839             sub PL_ADJ
840             # PL_ADJ($word,$number)
841             {
842 0     0 1 0 my ($str, $count) = @_;
843 0         0 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
844 0 0       0 return $str unless $word;
845 0   0     0 my $plural = postprocess $word, _PL_special_adjective($word,$count)
846             || $word;
847 0         0 return $pre.$plural.$post;
848             }
849              
850 3384 100 100 3384 0 23021 sub PL_eq { _PL_eq(@_, \&PL_N) || _PL_eq(@_, \&PL_V) || \&PL_ADJ; }
851 0     0 0 0 sub PL_N_eq { _PL_eq(@_, \&PL_N); }
852 0     0 0 0 sub PL_V_eq { _PL_eq(@_, \&PL_V); }
853 0     0 0 0 sub PL_ADJ_eq { _PL_eq(@_, \&PL_ADJ); }
854              
855             sub _PL_eq
856             {
857 3504     3504   5590 my ( $word1, $word2, $PL ) = @_;
858 3504         13590 my %classval = %classical;
859 3504         12519 %classical = %all_classical;
860 3504         5451 my $result = "";
861 3504 100 66     15299 $result = "eq" if !$result && $word1 eq $word2;
862 3504 100 100     9032 $result = "p:s" if !$result && $word1 eq &$PL($word2);
863 3504 100 100     9192 $result = "s:p" if !$result && &$PL($word1) eq $word2;
864 3504         7732 %classical = ();
865 3504 100 100     7897 $result = "p:s" if !$result && $word1 eq &$PL($word2);
866 3504 100 100     7597 $result = "s:p" if !$result && &$PL($word1) eq $word2;
867 3504         12553 %classical = %classval;
868              
869 3504 100 66     20211 if ($PL == \&PL || $PL == \&PL_N)
870             {
871 3384 50 66     7546 $result = "p:p"
872             if !$result && _PL_check_plurals_N($word1,$word2);
873 3384 50 66     7528 $result = "p:p"
874             if !$result && _PL_check_plurals_N($word2,$word1);
875             }
876 3504 50 33     18464 if ($PL == \&PL || $PL == \&PL_ADJ)
877             {
878 0 0 0     0 $result = "p:p"
879             if !$result && _PL_check_plurals_ADJ($word1,$word2,$PL);
880             }
881              
882 3504         17095 return $result;
883             }
884              
885             sub _PL_reg_plurals
886             {
887 3840     3840   207761 $_[0] =~ /($_[1])($_[2]\|\1$_[3]|$_[3]\|\1$_[2])/
888             }
889              
890             sub _PL_check_plurals_N
891             {
892 240     240   490 my $pair = "$_[0]|$_[1]";
893 240 50       618 foreach ( values %PL_sb_irregular_s ) { return 1 if $_ eq $pair; }
  1920         6651  
894 240 50       1187 foreach ( values %PL_sb_irregular ) { return 1 if $_ eq $pair; }
  10320         18354  
895              
896 240 50 33     537 return 1 if _PL_reg_plurals($pair, $PL_sb_C_a_ata, "as","ata")
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
897             || _PL_reg_plurals($pair, $PL_sb_C_is_ides, "is","ides")
898             || _PL_reg_plurals($pair, $PL_sb_C_a_ae, "s","e")
899             || _PL_reg_plurals($pair, $PL_sb_C_en_ina, "ens","ina")
900             || _PL_reg_plurals($pair, $PL_sb_C_um_a, "ums","a")
901             || _PL_reg_plurals($pair, $PL_sb_C_us_i, "uses","i")
902             || _PL_reg_plurals($pair, $PL_sb_C_on_a, "ons","a")
903             || _PL_reg_plurals($pair, $PL_sb_C_o_i, "os","i")
904             || _PL_reg_plurals($pair, $PL_sb_C_ex_ices, "exes","ices")
905             || _PL_reg_plurals($pair, $PL_sb_C_ix_ices, "ixes","ices")
906             || _PL_reg_plurals($pair, $PL_sb_C_i, "s","i")
907             || _PL_reg_plurals($pair, $PL_sb_C_im, "s","im")
908              
909             || _PL_reg_plurals($pair, '.*eau', "s","x")
910             || _PL_reg_plurals($pair, '.*ieu', "s","x")
911             || _PL_reg_plurals($pair, '.*tri', "xes","ces")
912             || _PL_reg_plurals($pair, '.{2,}[yia]n', "xes","ges");
913              
914              
915 240         7610 return 0;
916             }
917              
918             sub _PL_check_plurals_ADJ
919             {
920 0     0   0 my ( $word1a, $word2a ) = @_;
921 0         0 my ( $word1b, $word2b ) = @_;
922              
923 0 0       0 $word1a = '' unless $word1a =~ s/'s?$//;
924 0 0       0 $word2a = '' unless $word2a =~ s/'s?$//;
925 0 0       0 $word1b = '' unless $word1b =~ s/s'$//;
926 0 0       0 $word2b = '' unless $word2b =~ s/s'$//;
927              
928 0 0       0 if ($word1a)
929             {
930 0 0 0     0 return 1 if $word2a && ( _PL_check_plurals_N($word1a, $word2a)
      0        
931             || _PL_check_plurals_N($word2a, $word1a) );
932 0 0 0     0 return 1 if $word2b && ( _PL_check_plurals_N($word1a, $word2b)
      0        
933             || _PL_check_plurals_N($word2b, $word1a) );
934             }
935 0 0       0 if ($word1b)
936             {
937 0 0 0     0 return 1 if $word2a && ( _PL_check_plurals_N($word1b, $word2a)
      0        
938             || _PL_check_plurals_N($word2a, $word1b) );
939 0 0 0     0 return 1 if $word2b && ( _PL_check_plurals_N($word1b, $word2b)
      0        
940             || _PL_check_plurals_N($word2b, $word1b) );
941             }
942              
943              
944 0         0 return "";
945             }
946              
947             sub _PL_noun
948             {
949 8256     8256   11082 my ( $word, $count ) = @_;
950 8256         9005 my $value; # UTILITY VARIABLE
951              
952             # DEFAULT TO PLURAL
953              
954 8256 50 66     32722 $count = $persistent_count
955             if !defined($count) && defined($persistent_count);
956              
957 8256 100 33     40393 $count = (defined $count and $count=~/^($PL_count_one)$/io
958             or defined $count and $classical{zero}
959             and $count=~/^($PL_count_zero)$/io)
960             ? 1
961             : 2;
962              
963 8256 100       14396 return $word if $count==1;
964              
965             # HANDLE USER-DEFINED NOUNS
966              
967 8250 100       13523 return $value if defined($value = ud_match($word, @PL_sb_user_defined));
968              
969              
970             # HANDLE EMPTY WORD, SINGULAR COUNT AND UNINFLECTED PLURALS
971              
972 8247 50       15521 $word eq '' and return $word;
973              
974 8247 100       83610 $word =~ /^($PL_sb_uninflected)$/i
975             and return $word;
976              
977 7533 100 100     42454 $classical{herd} and $word =~ /^($PL_sb_uninflected_herd)$/i
978             and return $word;
979              
980              
981             # HANDLE COMPOUNDS ("Governor General", "mother-in-law", "aide-de-camp", ETC.)
982              
983 7491 100 66     50846 $word =~ /^(?:$PL_sb_postfix_adj)$/i
984             and $value = $2
985             and return _PL_noun($1,2)
986             . $value;
987              
988 7433 100 100     43659 $word =~ /^(?:$PL_sb_prep_dual_compound)$/i
989             and $value = [$2,$3]
990             and return _PL_noun($1,2)
991             . $value->[0]
992             . _PL_noun($value->[1]);
993              
994 7425 100 66     53585 $word =~ /^(?:$PL_sb_prep_compound)$/i
995             and $value = $2
996             and return _PL_noun($1,2)
997             . $value;
998              
999             # HANDLE PRONOUNS
1000              
1001 7303 100       40175 $word =~ /^((?:$PL_prep)\s+)($PL_pron_acc)$/i
1002             and return $1.$PL_pron_acc{lc($2)};
1003              
1004 7119 100       19708 $value = $PL_pron_nom{lc($word)}
1005             and return $value;
1006              
1007 6989 100       25839 $word =~ /^($PL_pron_acc)$/i
1008             and return $PL_pron_acc{lc($1)};
1009              
1010             # HANDLE ISOLATED IRREGULAR PLURALS
1011              
1012 6961 100 33     37509 $word =~ /(.*)\b($PL_sb_irregular)$/i
1013             and return $1
1014             . ( $PL_sb_irregular{$2} || $PL_sb_irregular{lc $2} );
1015 6629 100       34403 $word =~ /($PL_sb_U_man_mans)$/i
1016             and return "$1s";
1017 6365 100       24531 $word =~ /(\S*)quy$/i
1018             and return "$1quies";
1019 6357 100       17708 $word =~ /(\S*)(person)$/i and return $classical{persons}?"$1persons":"$1people";
    100          
1020              
1021             # HANDLE FAMILIES OF IRREGULAR PLURALS
1022              
1023 6317 100       17005 $word =~ /(.*)man$/i and return "$1men";
1024 6155 100       19871 $word =~ /(.*[ml])ouse$/i and return "$1ice";
1025 6107 100       14437 $word =~ /(.*)goose$/i and return "$1geese";
1026 6099 100       13200 $word =~ /(.*)tooth$/i and return "$1teeth";
1027 6091 100       14460 $word =~ /(.*)foot$/i and return "$1feet";
1028              
1029             # HANDLE UNASSIMILATED IMPORTS
1030              
1031 6081 100       13471 $word =~ /(.*)ceps$/i and return $word;
1032 6077 100       13451 $word =~ /(.*)zoon$/i and return "$1zoa";
1033 6053 100       16989 $word =~ /(.*[csx])is$/i and return "$1es";
1034 6021 100       24932 $word =~ /($PL_sb_U_ch_chs)ch$/i and return "$1chs";
1035 5997 100       21305 $word =~ /($PL_sb_U_ex_ices)ex$/i and return "$1ices";
1036 5973 100       20967 $word =~ /($PL_sb_U_ix_ices)ix$/i and return "$1ices";
1037 5957 100       31432 $word =~ /($PL_sb_U_um_a)um$/i and return "$1a";
1038 5885 100       35052 $word =~ /($PL_sb_U_us_i)us$/i and return "$1i";
1039 5813 100       25246 $word =~ /($PL_sb_U_on_a)on$/i and return "$1a";
1040 5741 100       22768 $word =~ /($PL_sb_U_a_ae)$/i and return "$1e";
1041              
1042             # HANDLE INCOMPLETELY ASSIMILATED IMPORTS
1043              
1044 5709 100       12712 if ($classical{ancient})
1045             {
1046 4060 100       11256 $word =~ /(.*)trix$/i and return "$1trices";
1047 4029 100       10586 $word =~ /(.*)eau$/i and return "$1eaux";
1048 3975 100       11744 $word =~ /(.*)ieu$/i and return "$1ieux";
1049 3963 100       13304 $word =~ /(.{2,}[yia])nx$/i and return "$1nges";
1050 3939 100       16243 $word =~ /($PL_sb_C_en_ina)en$/i and return "$1ina";
1051 3915 100       16714 $word =~ /($PL_sb_C_ex_ices)ex$/i and return "$1ices";
1052 3866 100       13712 $word =~ /($PL_sb_C_ix_ices)ix$/i and return "$1ices";
1053 3860 100       19986 $word =~ /($PL_sb_C_um_a)um$/i and return "$1a";
1054 3691 100       15342 $word =~ /($PL_sb_C_us_i)us$/i and return "$1i";
1055 3607 100       13941 $word =~ /($PL_sb_C_us_us)$/i and return "$1";
1056 3579 100       24297 $word =~ /($PL_sb_C_a_ae)$/i and return "$1e";
1057 3477 100       16588 $word =~ /($PL_sb_C_a_ata)a$/i and return "$1ata";
1058 3345 100       25694 $word =~ /($PL_sb_C_is_ides)is$/i and return "$1ides";
1059 3279 100       17318 $word =~ /($PL_sb_C_o_i)o$/i and return "$1i";
1060 3231 100       9989 $word =~ /($PL_sb_C_on_a)on$/i and return "$1a";
1061 3225 100       12946 $word =~ /$PL_sb_C_im$/i and return "${word}im";
1062 3207 100       19264 $word =~ /$PL_sb_C_i$/i and return "${word}i";
1063             }
1064              
1065              
1066             # HANDLE SINGULAR NOUNS ENDING IN ...s OR OTHER SILIBANTS
1067              
1068 4844 100       39877 $word =~ /^($PL_sb_singular_s)$/i and return "$1es";
1069 3506 100 66     9524 $word =~ /^([A-Z].*s)$/ and $classical{names} and return "$1es";
1070 3446 100       12012 $word =~ /^(.*[^z])(z)$/i and return "$1zzes";
1071 3430 100       17023 $word =~ /^(.*)([cs]h|x|zz|ss)$/i and return "$1$2es";
1072             # $word =~ /(.*)(us)$/i and return "$1$2es";
1073              
1074             # HANDLE ...f -> ...ves
1075              
1076 3201 100       9345 $word =~ /(.*[eao])lf$/i and return "$1lves";
1077 3145 100       9071 $word =~ /(.*[^d])eaf$/i and return "$1eaves";
1078 3117 100       7550 $word =~ /(.*[nlw])ife$/i and return "$1ives";
1079 3073 100       8025 $word =~ /(.*)arf$/i and return "$1arves";
1080              
1081             # HANDLE ...y
1082              
1083 3049 100       8676 $word =~ /(.*[aeiou])y$/i and return "$1ys";
1084 2989 100 100     7198 $word =~ /([A-Z].*y)$/ and $classical{names} and return "$1s";
1085 2972 100       7767 $word =~ /(.*)y$/i and return "$1ies";
1086              
1087             # HANDLE ...o
1088              
1089 2910 100       54473 $word =~ /$PL_sb_U_o_os$/i and return "${word}s";
1090 2638 100       7304 $word =~ /[aeiou]o$/i and return "${word}s";
1091 2566 100       6230 $word =~ /o$/i and return "${word}es";
1092              
1093              
1094             # OTHERWISE JUST ADD ...s
1095              
1096 2478         13687 return "${word}s";
1097             }
1098              
1099              
1100             sub _PL_special_verb
1101             {
1102 3685     3685   4594 my ( $word, $count ) = @_;
1103 3685 50 66     20567 $count = $persistent_count
1104             if !defined($count) && defined($persistent_count);
1105 3685 50 33     17286 $count = (defined $count and $count=~/^($PL_count_one)$/io or
1106             defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1
1107             : 2;
1108              
1109 3685 50       10867 return if $count=~/^($PL_count_one)$/io;
1110              
1111 3685         3536 my $value; # UTILITY VARIABLE
1112              
1113             # HANDLE USER-DEFINED VERBS
1114              
1115 3685 100       6186 return $value if defined($value = ud_match($word, @PL_v_user_defined));
1116              
1117             # HANDLE IRREGULAR PRESENT TENSE (SIMPLE AND COMPOUND)
1118              
1119 3683 100       18307 $word =~ /^($PL_v_irregular_pres)((\s.*)?)$/i
1120             and return $PL_v_irregular_pres{lc $1}.$2;
1121              
1122             # HANDLE IRREGULAR FUTURE, PRETERITE AND PERFECT TENSES
1123              
1124 3555 100       15134 $word =~ /^($PL_v_irregular_non_pres)((\s.*)?)$/i
1125             and return $word;
1126              
1127             # HANDLE PRESENT NEGATIONS (SIMPLE AND COMPOUND)
1128              
1129 3495 100       13629 $word =~ /^($PL_v_irregular_pres)(n't(\s.*)?)$/i
1130             and return $PL_v_irregular_pres{lc $1}.$2;
1131              
1132 3465 100       9024 $word =~ /^\S+n't\b/i
1133             and return $word;
1134              
1135             # HANDLE SPECIAL CASES
1136              
1137 3457 100       34594 $word =~ /^($PL_v_special_s)$/ and return;
1138 2841 100       7455 $word =~ /\s/ and return;
1139              
1140             # HANDLE STANDARD 3RD PERSON (CHOP THE ...(e)s OFF SINGLE WORDS)
1141              
1142 2689 100       14135 $word =~ /^(.*)([cs]h|[x]|zz|ss)es$/i and return "$1$2";
1143              
1144 2673 100       8491 $word =~ /^(..+)ies$/i and return "$1y";
1145              
1146 2664 100       19358 $word =~ /($PL_v_oes_oe)$/ and return substr($1,0,-1);
1147 2568 50       9456 $word =~ /^(.+)oes$/i and return "$1o";
1148              
1149 2568 100       15686 $word =~ /^(.*[^s])s$/i and return $1;
1150              
1151             # OTHERWISE, A REGULAR VERB (HANDLE ELSEWHERE)
1152              
1153 2393         12106 return;
1154             }
1155              
1156             sub _PL_general_verb
1157             {
1158 1654     1654   2396 my ( $word, $count ) = @_;
1159 1654 50 33     6974 $count = $persistent_count
1160             if !defined($count) && defined($persistent_count);
1161 1654 50 33     8101 $count = (defined $count and $count=~/^($PL_count_one)$/io or
1162             defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1
1163             : 2;
1164              
1165 1654 50       4928 return $word if $count=~/^($PL_count_one)$/io;
1166              
1167             # HANDLE AMBIGUOUS PRESENT TENSES (SIMPLE AND COMPOUND)
1168              
1169 1654 100       8520 $word =~ /^($PL_v_ambiguous_pres)((\s.*)?)$/i
1170             and return $PL_v_ambiguous_pres{lc $1}.$2;
1171              
1172             # HANDLE AMBIGUOUS PRETERITE AND PERFECT TENSES
1173              
1174 1650 100       7161 $word =~ /^($PL_v_ambiguous_non_pres)((\s.*)?)$/i
1175             and return $word;
1176              
1177             # OTHERWISE, 1st OR 2ND PERSON IS UNINFLECTED
1178              
1179 1632         7019 return $word;
1180              
1181             }
1182              
1183             sub _PL_special_adjective
1184             {
1185 1704     1704   2343 my ( $word, $count ) = @_;
1186 1704 50 66     7049 $count = $persistent_count
1187             if !defined($count) && defined($persistent_count);
1188 1704 100 66     8689 $count = (defined $count and $count=~/^($PL_count_one)$/io or
1189             defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1
1190             : 2;
1191              
1192 1704 100       5410 return $word if $count=~/^($PL_count_one)$/io;
1193              
1194              
1195             # HANDLE USER-DEFINED ADJECTIVES
1196              
1197 1700         1575 my $value;
1198 1700 100       2695 return $value if defined($value = ud_match($word, @PL_adj_user_defined));
1199              
1200             # HANDLE KNOWN CASES
1201              
1202 1697 100       6881 $word =~ /^($PL_adj_special)$/i
1203             and return $PL_adj_special{lc $1};
1204              
1205             # HANDLE POSSESSIVES
1206              
1207 1687 100       6366 $word =~ /^($PL_adj_poss)$/i
1208             and return $PL_adj_poss{lc $1};
1209              
1210 1671 100       4082 $word =~ /^(.*)'s?$/ and do { my $pl = PL_N($1);
  14         25  
1211 14 100       100 return "$pl'" . ($pl =~ m/s$/ ? "" : "s");
1212             };
1213              
1214             # OTHERWISE, NO IDEA
1215              
1216 1657         5902 return;
1217              
1218             }
1219              
1220              
1221             # 2. INDEFINITE ARTICLES
1222              
1223             # THIS PATTERN MATCHES STRINGS OF CAPITALS STARTING WITH A "VOWEL-SOUND"
1224             # CONSONANT FOLLOWED BY ANOTHER CONSONANT, AND WHICH ARE NOT LIKELY
1225             # TO BE REAL WORDS (OH, ALL RIGHT THEN, IT'S JUST MAGIC!)
1226              
1227             my $A_abbrev = q{
1228             (?! FJO | [HLMNS]Y. | RY[EO] | SQU
1229             | ( F[LR]? | [HL] | MN? | N | RH? | S[CHKLMNPTVW]? | X(YL)?) [AEIOU])
1230             [FHLMNRSX][A-Z]
1231             };
1232              
1233             # THIS PATTERN CODES THE BEGINNINGS OF ALL ENGLISH WORDS BEGINING WITH A
1234             # 'y' FOLLOWED BY A CONSONANT. ANY OTHER Y-CONSONANT PREFIX THEREFORE
1235             # IMPLIES AN ABBREVIATION.
1236              
1237             my $A_y_cons = 'y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)';
1238              
1239             # EXCEPTIONS TO EXCEPTIONS
1240              
1241             my $A_explicit_an = enclose join '|',
1242             (
1243             "euler",
1244             "hour(?!i)", "heir", "honest", "hono",
1245             "[fhlmnx]-?th",
1246             );
1247              
1248             sub A
1249             {
1250 109     109 0 20525 my ($str, $count) = @_;
1251 109         689 my ($pre, $word, $post) = ( $str =~ m/\A(\s*)(?:an?\s+)?(.+?)(\s*)\Z/i );
1252 109 50       243 return $str unless $word;
1253 109         201 my $result = _indef_article($word,$count);
1254 109         340 return $pre.$result.$post;
1255             }
1256              
1257 0     0 0 0 sub AN { goto &A }
1258              
1259             sub _indef_article
1260             {
1261 109     109   144 my ( $word, $count ) = @_;
1262              
1263 109 50 33     445 $count = $persistent_count
1264             if !defined($count) && defined($persistent_count);
1265              
1266 109 50 33     239 return "$count $word"
1267             if defined $count && $count!~/^($PL_count_one)$/io;
1268              
1269             # HANDLE USER-DEFINED VARIANTS
1270              
1271 109         101 my $value;
1272 109 50       178 return $value if defined($value = ud_match($word, @A_a_user_defined));
1273              
1274             # HANDLE SPECIAL CASES
1275              
1276 109 50       686 $word =~ /^($A_explicit_an)/i and return "an $word";
1277 109 50       228 $word =~ /^[aefhilmnorsx]$/i and return "an $word";
1278 109 50       234 $word =~ /^[bcdgjkpqtuvwyz]$/i and return "a $word";
1279              
1280              
1281             # HANDLE ABBREVIATIONS
1282              
1283 109 50       431 $word =~ /^($A_abbrev)/ox and return "an $word";
1284 109 50       276 $word =~ /^[aefhilmnorsx][.-]/i and return "an $word";
1285 109 100       243 $word =~ /^[a-z][.-]/i and return "a $word";
1286              
1287             # HANDLE CONSONANTS
1288              
1289 98 100       297 $word =~ /^[^aeiouy]/i and return "a $word";
1290              
1291             # HANDLE SPECIAL VOWEL-FORMS
1292              
1293 45 100       114 $word =~ /^e[uw]/i and return "a $word";
1294 39 100       85 $word =~ /^onc?e\b/i and return "a $word";
1295 37 100       109 $word =~ /^uni([^nmd]|mo)/i and return "a $word";
1296 21 100       80 $word =~ /^u[bcfhjkqrst][aeiou]/i and return "a $word";
1297              
1298             # HANDLE SPECIAL CAPITALS
1299              
1300 7 100       95 $word =~ /^U[NK][AIEO]?/ and return "a $word";
1301              
1302             # HANDLE VOWELS
1303              
1304 5 50       18 $word =~ /^[aeiou]/i and return "an $word";
1305              
1306             # HANDLE y... (BEFORE CERTAIN CONSONANTS IMPLIES (UNNATURALIZED) "i.." SOUND)
1307              
1308 5 50       107 $word =~ /^($A_y_cons)/io and return "an $word";
1309              
1310             # OTHERWISE, GUESS "a"
1311 5         26 return "a $word";
1312             }
1313              
1314             # 2. TRANSLATE ZERO-QUANTIFIED $word TO "no PL($word)"
1315              
1316             sub NO
1317             {
1318 4     4 0 13 my ($str, $count) = @_;
1319 4         30 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
1320              
1321 4 50 33     17 $count = $persistent_count
1322             if !defined($count) && defined($persistent_count);
1323 4 100       12 $count = 0 unless $count;
1324              
1325 4 100       69 return "$pre$count " . PL($word,$count) . $post
1326             unless $count =~ /^$PL_count_zero$/;
1327 2         166 return "${pre}no ". PL($word,0) . $post ;
1328             }
1329              
1330              
1331             # PARTICIPLES
1332              
1333             sub PART_PRES
1334             {
1335 6     6 0 18 local $_ = PL_V(shift,2);
1336 6 100 33     125 s/ie$/y/
      33        
      66        
      66        
      100        
      66        
1337             or s/ue$/u/
1338             or s/([auy])e$/$1/
1339             or s/ski$/ski/
1340             or s/i$//
1341             or s/([^e])e$/$1/
1342             or m/er$/
1343             or s/([^aeiou][aeiouy]([bdgmnprst]))$/$1$2/;
1344 6         137 return "${_}ing";
1345             }
1346              
1347              
1348              
1349             # NUMERICAL INFLECTIONS
1350              
1351             my %nth =
1352             (
1353             0 => 'th',
1354             1 => 'st',
1355             2 => 'nd',
1356             3 => 'rd',
1357             4 => 'th',
1358             5 => 'th',
1359             6 => 'th',
1360             7 => 'th',
1361             8 => 'th',
1362             9 => 'th',
1363             11 => 'th',
1364             12 => 'th',
1365             13 => 'th',
1366             );
1367              
1368              
1369             my %ordinal;
1370             @ordinal{qw(ty one two three five eight nine twelve )}=
1371             qw(tieth first second third fifth eighth ninth twelfth);
1372              
1373             my $ordinal_suff = join '|', keys %ordinal, "";
1374              
1375             $ordinal{""} = 'th';
1376              
1377             sub ORD($)
1378             {
1379 164     164 0 251 my $num = shift;
1380 164 100       433 if ($num =~ /\d/) {
1381 82   66     619 return $num . ($nth{$num%100} || $nth{$num%10});
1382             }
1383             else {
1384 82         748 $num =~ s/($ordinal_suff)\Z/$ordinal{$1}/;
1385 82         437 return $num;
1386             }
1387             }
1388              
1389              
1390             my %default_args =
1391             (
1392             'group' => 0,
1393             'comma' => ',',
1394             'and' => 'and',
1395             'zero' => 'zero',
1396             'one' => 'one',
1397             'decimal' => 'point',
1398             );
1399              
1400             my @unit = ('',qw(one two three four five six seven eight nine));
1401             my @teen = qw(ten eleven twelve thirteen fourteen
1402             fifteen sixteen seventeen eighteen nineteen);
1403             my @ten = ('','',qw(twenty thirty forty fifty sixty seventy eighty ninety));
1404             my @mill = map { (my $val=$_) =~ s/_/illion/; " $val" }
1405             ('',qw(thousand m_ b_ tr_ quadr_ quint_ sext_ sept_ oct_ non_ dec_));
1406              
1407              
1408 1120   100 1120 0 4113 sub mill { my $ind = $_[0]||0;
1409 1120 50       2266 die "Number out of range\n" if $ind > $#mill;
1410 1120 50       6919 return $ind<@mill ? $mill[$ind] : ' ???illion'; }
1411              
1412 755     755 0 1976 sub unit { return $unit[$_[0]]. mill($_[1]); }
1413              
1414             sub ten
1415             {
1416 626 100 66 626 0 3725 return $ten[$_[0]] . ($_[0]&&$_[1]?'-':'') . $unit[$_[1]] . mill($_[2])
    100          
1417             if $_[0] ne '1';
1418 377   100     2456 return $teen[$_[1]]. $mill[$_[2]||0];
1419             }
1420              
1421             sub hund
1422             {
1423 128 100 66 128 0 619 return unit($_[0]) . " hundred" . ($_[1] || $_[2] ? " $_[4] " : '')
    100          
1424             . ten($_[1],$_[2]) . mill($_[3]) . ', ' if $_[0];
1425 24 100 66     142 return ten($_[1],$_[2]) . mill($_[3]) . ', ' if $_[1] || $_[2];
1426 12         71 return '';
1427             }
1428              
1429              
1430             sub enword
1431             {
1432 1039     1039 0 1866 my ($num,$group,$zero,$one,$comma,$and) = @_;
1433              
1434 1039 100       3931 if ($group==1)
    100          
    100          
    100          
    100          
1435             {
1436 85 100       295 $num =~ s/(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")."$comma " /eg;
  321 100       1244  
1437             }
1438             elsif ($group==2)
1439             {
1440 63 100       212 $num =~ s/(\d)(\d)/ ($1 ? ten($1,$2) : $2 ? " $zero " . unit($2) : " $zero $zero") . "$comma " /eg;
  102 100       325  
1441 63 100       228 $num =~ s/(\d)/ ($1 ? unit($1) :" $zero")."$comma " /e;
  30         97  
1442             }
1443             elsif ($group==3)
1444             {
1445 63 100       152 $num =~ s/(\d)(\d)(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")." ".($2 ? ten($2,$3) : $3 ? " $zero " . unit($3) : " $zero $zero") . "$comma " /eg;
  55 100       298  
    100          
    100          
1446 63 50       174 $num =~ s/(\d)(\d)/ ($1 ? ten($1,$2) : $2 ? " $zero " . unit($2) : " $zero $zero") . "$comma " /e;
  23 100       79  
1447 63 100       162 $num =~ s/(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")."$comma " /e;
  23 100       104  
1448             }
1449             elsif ($num+0==0) {
1450 48         61 $num = $zero;
1451             }
1452             elsif ($num+0==1) {
1453 44         60 $num = $one;
1454             }
1455             else {
1456 736         1109 $num =~ s/\A\s*0+//;
1457 736         1025 my $mill = 0;
1458 736         1914 1 while $num =~ s/(\d)(\d)(\d)(?=\D*\Z)/ hund($1,$2,$3,$mill++,$and) /e;
  128         301  
1459 736         1833 $num =~ s/(\d)(\d)(?=\D*\Z)/ ten($1,$2,$mill)."$comma " /e;
  356         621  
1460 736         2215 $num =~ s/(\d)(?=\D*\Z)/ unit($1,$mill) . "$comma "/e;
  343         630  
1461             }
1462 1039         3103 return $num;
1463             }
1464              
1465             sub NUMWORDS
1466             {
1467 1281     1281 0 93573 my $num = shift;
1468              
1469 1281 100 66     3961 if (@_ % 2 and require Carp) {
1470 55         573 die "Missing value in option list (odd number of option args) at"
1471             . join ' line ', (caller)[1,2];
1472             }
1473              
1474 1226         6167 my %arg = ( %default_args, @_ );
1475 1226         2250 my $group = $arg{group};
1476              
1477             # Handle "stylistic" conversions (up to a given threshold)...
1478 1226 100 100     4613 if (exists $arg{threshold} && $num > $arg{threshold}) {
1479 230         623 my ($whole, $frac) = split /[.]/, $num;
1480 230         596 while ($arg{comma}) {
1481 230 100       599 $whole =~ s{ (\d) ( \d{3}(?:,|\z) ) }{$1,$2}xms
1482             or last;
1483             }
1484 230 100       1009 return $frac ? "$whole.$frac" : $whole;
1485             }
1486              
1487 996 50       4340 die "Bad chunking option: $group\n" unless $group =~ /\A[0-3]\Z/;
1488 996 50       2929 my $sign = ($num =~ /\A\s*\+/) ? "plus"
    50          
1489             : ($num =~ /\A\s*\-/) ? "minus"
1490             : '';
1491              
1492 996         1824 my ($zero, $one) = @arg{'zero','one'};
1493 996         1286 my $comma = $arg{comma};
1494 996         1108 my $and = $arg{'and'};
1495              
1496 996         2342 my $ord = $num =~ s/(st|nd|rd|th)\Z//;
1497 996 100       3625 my @chunks = ($arg{decimal})
    50          
1498             ? $group ? split(/\./, $num) : split(/\./, $num, 2)
1499             : ($num);
1500              
1501 996         1319 my $first = 1;
1502              
1503 996 100       1912 if ($chunks[0] eq '') { $first=0; shift @chunks; }
  6         8  
  6         10  
1504              
1505 996         1561 foreach ( @chunks )
1506             {
1507 1039         1665 s/\D//g;
1508 1039 100       1926 $_ = '0' unless $_;
1509              
1510 1039 100 100     8460 if (!$group && !$first) { $_ = enword($_,1,$zero,$one,$comma,$and) }
  22         44  
1511 1017         1818 else { $_ = enword($_,$group,$zero,$one,$comma,$and) }
1512              
1513 1039         4093 s/, \Z//;
1514 1039         2168 s/\s+,/,/g;
1515 1039 100 100     4493 s/, (\S+)\s+\Z/ $and $1/ if !$group and $first;
1516 1039         3414 s/\s+/ /g;
1517 1039         4819 s/(\A\s|\s\Z)//g;
1518 1039 100       3404 $first = '' if $first;
1519             }
1520              
1521 996         1578 my @numchunks = ();
1522 996 100       1621 if ($first =~ /0/)
1523             {
1524 6         13 unshift @chunks, '';
1525             }
1526             else
1527             {
1528 990         4666 @numchunks = split /\Q$comma /, $chunks[0];
1529             }
1530              
1531 996 100 100     2940 $numchunks[-1] =~ s/($ordinal_suff)\Z/$ordinal{$1}/
1532             if $ord and @numchunks;
1533              
1534 996         2743 foreach (@chunks[1..$#chunks])
1535             {
1536 49         89 push @numchunks, $arg{decimal};
1537 49         231 push @numchunks, split /\Q$comma /;
1538             }
1539              
1540 996 50       2445 if (wantarray)
    100          
1541             {
1542 0 0       0 unshift @numchunks, $sign if $sign;
1543             return @numchunks
1544 0         0 }
1545             elsif ($group)
1546             {
1547 165 50       1304 return ($sign?"$sign ":'') . join ", ", @numchunks;
1548             }
1549             else
1550             {
1551 831 50       1575 $num = ($sign?"$sign ":'') . shift @numchunks;
1552 831         2453 $first = ($num !~ /$arg{decimal}\Z/);
1553 831         1355 foreach ( @numchunks )
1554             {
1555 176 100       629 if (/\A$arg{decimal}\Z/)
    100          
1556             {
1557 19         34 $num .= " $_";
1558 19         39 $first = 0;
1559             }
1560             elsif ($first)
1561             {
1562 70         208 $num .= "$comma $_";
1563             }
1564             else
1565             {
1566 87         182 $num .= " $_";
1567             }
1568             }
1569 831         4250 return $num;
1570             }
1571             }
1572              
1573             # Join words with commas and a trailing 'and' (when appropriate)...
1574              
1575             sub WORDLIST {
1576 20     20 0 36 my %opt;
1577             my @words;
1578              
1579 20         38 for my $arg (@_) {
1580 61 100       85 if (ref $arg eq 'HASH' ) {
1581 16         21 %opt = (%opt, %{$arg});
  16         71  
1582             }
1583             else {
1584 45         74 push @words, $arg;
1585             }
1586             }
1587              
1588 20 50       47 return "" if @words == 0;
1589 20 100       54 return "$words[0]" if @words == 1;
1590              
1591 15 100       26 my $conj = exists($opt{conj}) ? $opt{conj} : 'and';
1592 15 100       31 if (@words == 2) {
1593 5         24 $conj =~ s/^ (?=[^\W\d_]) | (?<=[^\W\d_]) $/ /gxms;
1594 5         27 return "$words[0]$conj$words[1]";
1595             }
1596              
1597 10 100       48 my $sep = exists $opt{sep} ? $opt{sep}
    50          
1598             : grep(/,/, @words) ? q{; }
1599             : q{, }
1600             ;
1601              
1602 10 100       31 my $final_sep = !exists $opt{final_sep} ? "$sep $conj"
    100          
1603             : length($opt{final_sep}) == 0 ? $conj
1604             : "$opt{final_sep} $conj"
1605             ;
1606 10         34 $final_sep =~ s/\s+/ /gmxs;
1607 10         51 $final_sep =~ s/^ (?=[^\W\d_]) | (?<=[^\W\d_]) $/ /gxms;
1608              
1609 10         69 return join($sep, @words[0,@words-2]) . "$final_sep$words[-1]";
1610             }
1611              
1612              
1613              
1614             1;
1615              
1616             __END__