File Coverage

blib/lib/Lingua/ENG/Inflect.pm
Criterion Covered Total %
statement 342 401 85.2
branch 359 440 81.5
condition 122 252 48.4
subroutine 38 49 77.5
pod 9 33 27.2
total 870 1175 74.0


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