File Coverage

blib/lib/Lingua/ENG/Inflect.pm
Criterion Covered Total %
statement 338 398 84.9
branch 359 440 81.5
condition 122 252 48.4
subroutine 36 48 75.0
pod 9 33 27.2
total 864 1171 73.7


line stmt bran cond sub pod time code
1             package Lingua::ENG::Inflect;
2             # ABSTRACT: Plural inflection for ENG.
3              
4 14     14   84855 use 5.10.1;
  14         57  
5 14     14   82 use strict;
  14         26  
  14         562  
6 14     14   83 use vars qw(@EXPORT_OK %EXPORT_TAGS @ISA);
  14         24  
  14         1171  
7 14     14   19941 use Env;
  14         43844  
  14         111  
8              
9             require Exporter;
10             @ISA = qw(Exporter);
11             our $VERSION = '0.2603230';
12              
13             %EXPORT_TAGS =
14             (
15             ALL => [ qw( classical inflect
16             PL PL_N PL_V PL_ADJ NO NUM A AN
17             PL_eq PL_N_eq PL_V_eq PL_ADJ_eq
18             PART_PRES
19             ORD
20             NUMWORDS
21             WORDLIST
22             def_noun def_verb def_adj def_a def_an )],
23              
24             INFLECTIONS => [ qw( classical inflect
25             PL PL_N PL_V PL_ADJ PL_eq
26             NO NUM A AN PART_PRES )],
27              
28             PLURALS => [ qw( classical inflect
29             PL PL_N PL_V PL_ADJ NO NUM
30             PL_eq PL_N_eq PL_V_eq PL_ADJ_eq )],
31              
32             COMPARISONS => [ qw( classical
33             PL_eq PL_N_eq PL_V_eq PL_ADJ_eq )],
34              
35             ARTICLES => [ qw( classical inflect NUM A AN )],
36              
37             NUMERICAL => [ qw( ORD NUMWORDS )],
38              
39             USER_DEFINED => [ qw( def_noun def_verb def_adj def_a def_an )],
40             );
41              
42             Exporter::export_ok_tags(qw( ALL ));
43              
44             # SUPPORT CLASSICAL PLURALIZATIONS
45              
46             my %def_classical = (
47             all => 0,
48             zero => 0,
49             herd => 0,
50             names => 1,
51             persons => 0,
52             ancient => 0,
53             );
54              
55             my %all_classical = (
56             all => 1,
57             zero => 1,
58             herd => 1,
59             names => 1,
60             persons => 1,
61             ancient => 1,
62             );
63              
64             my %classical = %def_classical;
65              
66             my $classical_mode = join '|', keys %all_classical;
67             $classical_mode = qr/^(?:$classical_mode)$/;
68              
69             sub classical
70             {
71 1712 100   1712 0 479410 if (!@_) {
72 1         9 %classical = %all_classical;
73 1         5 return;
74             }
75 1711 100 100     5609 if (@_==1 && $_[0] !~ $classical_mode) {
76 5 100       33 %classical = $_[0] ? %all_classical : ();
77 5         11 return;
78             }
79 1706         4384 while (@_) {
80 2552         4688 my $arg = shift;
81 2552 50       18730 if ($arg !~ $classical_mode) {
82 0         0 die "Unknown classical mode ($arg)\n";
83             }
84 2552 100 66     17550 if (@_ && $_[0] !~ $classical_mode) { $classical{$arg} = shift; }
  2546         8678  
85 6         18 else { $classical{$arg} = 1; }
86              
87 2552 100       14452 if ($arg eq 'all') {
88 1695 100       13438 %classical = $classical{all} ? %all_classical : ();
89             }
90             }
91             }
92              
93             my $persistent_count;
94              
95             sub NUM # (;$count,$show)
96             {
97 0 0   0 0 0 if (defined $_[0])
98             {
99 0         0 $persistent_count = $_[0];
100 0 0 0     0 return $_[0] if !defined($_[1]) || $_[1];
101             }
102             else
103             {
104 0         0 $persistent_count = undef;
105             }
106 0         0 return '';
107             }
108              
109              
110             # 0. PERFORM GENERAL INFLECTIONS IN A STRING
111              
112 602     602 0 1559 sub enclose { "(?:$_[0])" }
113              
114             sub inflect
115             {
116 0     0 0 0 my $save_persistent_count = $persistent_count;
117 0         0 my @sections = split /(NUM\([^)]*\))/, $_[0];
118 0         0 my $inflection = "";
119              
120 0         0 foreach ( @sections )
121             {
122 0 0       0 unless (s/NUM\(\s*?(?:([^),]*)(?:,([^)]*))?)?\)/ NUM($1,$2) /xe)
  0         0  
123             {
124 0   0     0 1 while
      0        
      0        
      0        
      0        
      0        
      0        
      0        
125 0         0 s/\bPL \( ([^),]*) (, ([^)]*) )? \) / PL($1,$3) /xeg
126 0         0 || s/\bPL_N \( ([^),]*) (, ([^)]*) )? \) / PL_N($1,$3) /xeg
127 0         0 || s/\bPL_V \( ([^),]*) (, ([^)]*) )? \) / PL_V($1,$3) /xeg
128 0         0 || s/\bPL_ADJ \( ([^),]*) (, ([^)]*) )? \) / PL_ADJ($1,$3) /xeg
129 0         0 || s/\bAN? \( ([^),]*) (, ([^)]*) )? \) / A($1,$3) /xeg
130 0         0 || s/\bNO \( ([^),]*) (, ([^)]*) )? \) / NO($1,$3) /xeg
131 0         0 || s/\bORD \( ([^)]*) \) / ORD($1) /xeg
132 0         0 || s/\bNUMWORDS \( ([^)]*) \) / NUMWORDS($1) /xeg
133 0         0 || s/\bPART_PRES \( ([^)]*) \) / PART_PRES($1) /xeg
134             }
135              
136 0         0 $inflection .= $_;
137             }
138              
139 0         0 $persistent_count = $save_persistent_count;
140 0         0 return $inflection;
141             }
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              
407             # UNCONDITIONAL "..ch" -> "..chs"
408              
409             my $PL_sb_U_ch_chs = enclose join "|", map { substr($_,0,-2) }
410             qw(
411             czech eunuch stomach
412             );
413              
414             # UNCONDITIONAL "..[ei]x" -> "..ices"
415              
416             my $PL_sb_U_ex_ices = enclose join "|", map { substr($_,0,-2) }
417             (
418             "codex", "murex", "silex",
419             );
420              
421             my $PL_sb_U_ix_ices = enclose join "|", map { substr($_,0,-2) }
422             (
423             "radix", "helix",
424             );
425              
426             # CLASSICAL "..[ei]x" -> "..ices"
427              
428             my $PL_sb_C_ex_ices = enclose join "|", map { substr($_,0,-2) }
429             (
430             "vortex", "vertex", "cortex", "latex",
431             "pontifex", "apex", "index", "simplex",
432             );
433              
434             my $PL_sb_C_ix_ices = enclose join "|", map { substr($_,0,-2) }
435             (
436             "appendix",
437             );
438              
439             # ARABIC: ".." -> "..i"
440              
441             my $PL_sb_C_i = enclose join "|",
442             (
443             "afrit", "afreet", "efreet",
444             );
445              
446             # HEBREW: ".." -> "..im"
447              
448             my $PL_sb_C_im = enclose join "|",
449             (
450             "goy", "seraph", "cherub",
451             );
452              
453             # UNCONDITIONAL "..man" -> "..mans"
454              
455             my $PL_sb_U_man_mans = enclose join "|",
456             qw(
457             ataman caiman cayman ceriman
458             desman dolman farman harman hetman
459             human leman ottoman shaman talisman
460             Alabaman Bahaman Burman German
461             Hiroshiman Liman Nakayaman Norman Oklahoman
462             Panaman Roman Selman Sonaman Tacoman Yakiman
463             Yokohaman Yuman
464             );
465              
466             my @PL_sb_uninflected_s =
467             (
468             # PAIRS OR GROUPS SUBSUMED TO A SINGULAR...
469             "breeches", "britches", "pajamas", "pyjamas", "clippers", "gallows",
470             "hijinks", "headquarters", "pliers", "scissors", "testes", "herpes",
471             "pincers", "shears", "proceedings", "trousers",
472              
473             # UNASSIMILATED LATIN 4th DECLENSION
474              
475             "cantus", "coitus", "nexus",
476              
477             # RECENT IMPORTS...
478             "contretemps", "corps", "debris",
479             ".*ois", "siemens",
480            
481             # DISEASES
482             ".*measles", "mumps",
483              
484             # MISCELLANEOUS OTHERS...
485             "diabetes", "jackanapes", "series", "species", "rabies",
486             "chassis", "innings", "news", "mews", "haggis",
487             );
488              
489             my $PL_sb_uninflected_herd = enclose join "|",
490             # DON'T INFLECT IN CLASSICAL MODE, OTHERWISE NORMAL INFLECTION
491             (
492             "wildebeest", "swine", "eland", "bison", "buffalo",
493             "elk", "rhinoceros", 'zucchini',
494             'caribou', 'dace', 'grouse', 'guinea[- ]fowl',
495             'haddock', 'hake', 'halibut', 'herring', 'mackerel',
496             'pickerel', 'pike', 'roe', 'seed', 'shad',
497             'snipe', 'teal', 'turbot', 'water[- ]fowl',
498             );
499              
500             my $PL_sb_uninflected = enclose join "|",
501             (
502             # SOME FISH AND HERD ANIMALS
503             ".*fish", "tuna", "salmon", "mackerel", "trout",
504             "bream", "sea[- ]bass", "carp", "cod", "flounder", "whiting",
505              
506             ".*deer", ".*sheep", "moose",
507              
508             # ALL NATIONALS ENDING IN -ese
509             "Portuguese", "Amoyese", "Borghese", "Congoese", "Faroese",
510             "Foochowese", "Genevese", "Genoese", "Gilbertese", "Hottentotese",
511             "Kiplingese", "Kongoese", "Lucchese", "Maltese", "Nankingese",
512             "Niasese", "Pekingese", "Piedmontese", "Pistoiese", "Sarawakese",
513             "Shavese", "Vermontese", "Wenchowese", "Yengeese",
514             ".*[nrlm]ese",
515              
516             # SOME WORDS ENDING IN ...s (OFTEN PAIRS TAKEN AS A WHOLE)
517              
518             @PL_sb_uninflected_s,
519              
520             # DISEASES
521             ".*pox",
522              
523              
524             # OTHER ODDITIES
525             "graffiti", "djinn", 'samuri',
526             '.*craft$', 'offspring', 'pence', 'quid', 'hertz',
527             );
528              
529             # SINGULAR WORDS ENDING IN ...s (ALL INFLECT WITH ...es)
530              
531             my $PL_sb_singular_s = enclose join '|',
532             (
533             ".*ss",
534             "acropolis", "aegis", "alias", "asbestos", "bathos", "bias",
535             "bronchitis", "bursitis", "caddis", "cannabis",
536             "canvas", "chaos", "cosmos", "dais", "digitalis",
537             "epidermis", "ethos", "eyas", "gas", "glottis",
538             "hubris", "ibis", "lens", "mantis", "marquis", "metropolis",
539             "pathos", "pelvis", "polis", "rhinoceros",
540             "sassafras", "trellis", ".*us", "[A-Z].*es",
541            
542             @PL_sb_C_is_ides,
543             );
544              
545             my $PL_v_special_s = enclose join '|',
546             (
547             $PL_sb_singular_s,
548             @PL_sb_uninflected_s,
549             keys %PL_sb_irregular_s,
550             '(.*[csx])is',
551             '(.*)ceps',
552             '[A-Z].*s',
553             );
554              
555             my %PL_sb_postfix_adj = (
556             'general' => ['(?!major|lieutenant|brigadier|adjutant)\S+'],
557             'martial' => [qw(court)],
558             );
559              
560             foreach (keys %PL_sb_postfix_adj) {
561             $PL_sb_postfix_adj{$_} = enclose
562             enclose(join('|', @{$PL_sb_postfix_adj{$_}}))
563             . "(?=(?:-|\\s+)$_)";
564             }
565              
566             my $PL_sb_postfix_adj = '(' . join('|', values %PL_sb_postfix_adj) . ')(.*)';
567              
568             my $PL_sb_military = 'major|lieutenant|brigadier|adjutant|quartermaster';
569             my $PL_sb_general = '((?!'.$PL_sb_military.').*?)((-|\s+)general)';
570              
571             my $PL_prep = enclose join '|', qw (
572             about above across after among around at athwart before behind
573             below beneath beside besides between betwixt beyond but by
574             during except for from in into near of off on onto out over
575             since till to under until unto upon with
576             );
577              
578             my $PL_sb_prep_dual_compound = '(.*?)((?:-|\s+)(?:'.$PL_prep.'|d[eua])(?:-|\s+))a(?:-|\s+)(.*)';
579              
580             my $PL_sb_prep_compound = '(.*?)((-|\s+)('.$PL_prep.'|d[eua])((-|\s+)(.*))?)';
581              
582              
583             my %PL_pron_nom =
584             (
585             # NOMINATIVE REFLEXIVE
586              
587             "i" => "we", "myself" => "ourselves",
588             "you" => "you", "yourself" => "yourselves",
589             "she" => "they", "herself" => "themselves",
590             "he" => "they", "himself" => "themselves",
591             "it" => "they", "itself" => "themselves",
592             "they" => "they", "themself" => "themselves",
593              
594             # POSSESSIVE
595              
596             "mine" => "ours",
597             "yours" => "yours",
598             "hers" => "theirs",
599             "his" => "theirs",
600             "its" => "theirs",
601             "theirs" => "theirs",
602             );
603              
604             my %PL_pron_acc =
605             (
606             # ACCUSATIVE REFLEXIVE
607              
608             "me" => "us", "myself" => "ourselves",
609             "you" => "you", "yourself" => "yourselves",
610             "her" => "them", "herself" => "themselves",
611             "him" => "them", "himself" => "themselves",
612             "it" => "them", "itself" => "themselves",
613             "them" => "them", "themself" => "themselves",
614             );
615              
616             my $PL_pron_acc = enclose join '|', keys %PL_pron_acc;
617              
618             my %PL_v_irregular_pres =
619             (
620             # 1st PERS. SING. 2ND PERS. SING. 3RD PERS. SINGULAR
621             # 3RD PERS. (INDET.)
622              
623             "am" => "are", "are" => "are", "is" => "are",
624             "was" => "were", "were" => "were", "was" => "were",
625             "have" => "have", "have" => "have", "has" => "have",
626             "do" => "do", "do" => "do", "does" => "do",
627             );
628              
629             my $PL_v_irregular_pres = enclose join '|', keys %PL_v_irregular_pres;
630              
631             my %PL_v_ambiguous_pres =
632             (
633             # 1st PERS. SING. 2ND PERS. SING. 3RD PERS. SINGULAR
634             # 3RD PERS. (INDET.)
635              
636             "act" => "act", "act" => "act", "acts" => "act",
637             "blame" => "blame", "blame" => "blame", "blames" => "blame",
638             "can" => "can", "can" => "can", "can" => "can",
639             "must" => "must", "must" => "must", "must" => "must",
640             "fly" => "fly", "fly" => "fly", "flies" => "fly",
641             "copy" => "copy", "copy" => "copy", "copies" => "copy",
642             "drink" => "drink", "drink" => "drink", "drinks" => "drink",
643             "fight" => "fight", "fight" => "fight", "fights" => "fight",
644             "fire" => "fire", "fire" => "fire", "fires" => "fire",
645             "like" => "like", "like" => "like", "likes" => "like",
646             "look" => "look", "look" => "look", "looks" => "look",
647             "make" => "make", "make" => "make", "makes" => "make",
648             "reach" => "reach", "reach" => "reach", "reaches" => "reach",
649             "run" => "run", "run" => "run", "runs" => "run",
650             "sink" => "sink", "sink" => "sink", "sinks" => "sink",
651             "sleep" => "sleep", "sleep" => "sleep", "sleeps" => "sleep",
652             "view" => "view", "view" => "view", "views" => "view",
653             );
654              
655             my $PL_v_ambiguous_pres = enclose join '|', keys %PL_v_ambiguous_pres;
656              
657              
658             my $PL_v_irregular_non_pres = enclose join '|',
659             (
660             "did", "had", "ate", "made", "put",
661             "spent", "fought", "sank", "gave", "sought",
662             "shall", "could", "ought", "should",
663             );
664              
665             my $PL_v_ambiguous_non_pres = enclose join '|',
666             (
667             "thought", "saw", "bent", "will", "might", "cut",
668             );
669              
670             # "..oes" -> "..oe" (the rest are "..oes" -> "o")
671              
672             my $PL_v_oes_oe = enclose join "|",
673             qw(
674             .*shoes .*hoes .*toes
675             canoes floes oboes roes throes woes
676             );
677              
678             my $PL_count_zero = enclose join '|',
679             (
680             0, "no", "zero", "nil"
681             );
682              
683             my $PL_count_one = enclose join '|',
684             (
685             1, "a", "an", "one", "each", "every", "this", "that",
686             );
687              
688             my %PL_adj_special =
689             (
690             "a" => "some", "an" => "some",
691             "this" => "these", "that" => "those",
692             );
693             my $PL_adj_special = enclose join '|', keys %PL_adj_special;
694              
695             my %PL_adj_poss =
696             (
697             "my" => "our",
698             "your" => "your",
699             "its" => "their",
700             "her" => "their",
701             "his" => "their",
702             "their" => "their",
703             );
704             my $PL_adj_poss = enclose join '|', keys %PL_adj_poss;
705              
706              
707             sub checkpat
708             {
709 0     0 0 0 local $SIG{__WARN__} = sub {0};
  6     6   48  
710 6 50 33     673 do {$@ =~ s/at.*?$//;
  0         0  
711 0         0 die "\nBad user-defined singular pattern:\n\t$@\n"}
712             if (!eval "'' =~ m/$_[0]/; 1;" or $@);
713 6         47 return @_;
714             }
715              
716             sub checkpatsubs
717             {
718 6     6 0 21 checkpat($_[0]);
719 6 50       17 if (defined $_[1])
720             {
721 6     0   47 local $SIG{__WARN__} = sub {0};
  0         0  
722 6 50 33     563 do {$@ =~ s/at.*?$//;
  0         0  
723 0         0 die "\nBad user-defined plural string: '$_[1]'\n\t$@\n"}
724             if (!eval "qq{$_[1]}; 1;" or $@);
725             }
726 6         46 return @_;
727             }
728              
729             my @PL_sb_user_defined = ();
730             my @PL_v_user_defined = ();
731             my @PL_adj_user_defined = ();
732             my @A_a_user_defined = ();
733              
734             sub def_noun
735             {
736 2     2 1 31 unshift @PL_sb_user_defined, checkpatsubs(@_);
737 2         7 return 1;
738             }
739              
740             sub def_verb
741             {
742 1     1 1 9 unshift @PL_v_user_defined, checkpatsubs(@_[4,5]);
743 1         5 unshift @PL_v_user_defined, checkpatsubs(@_[2,3]);
744 1         5 unshift @PL_v_user_defined, checkpatsubs(@_[0,1]);
745 1         4 return 1;
746             }
747              
748             sub def_adj
749             {
750 1     1 1 8 unshift @PL_adj_user_defined, checkpatsubs(@_);
751 1         4 return 1;
752             }
753              
754             sub def_a
755             {
756 0     0 1 0 unshift @A_a_user_defined, checkpat(@_,'a');
757 0         0 return 1;
758             }
759              
760             sub def_an
761             {
762 0     0 1 0 unshift @A_a_user_defined, checkpat(@_,'an');
763 0         0 return 1;
764             }
765              
766             sub ud_match
767             {
768 13744     13744 0 28954 my $word = shift;
769 13744         39443 for (my $i=0; $i < @_; $i+=2)
770             {
771 44 100       536 if ($word =~ /^(?:$_[$i])$/i)
772             {
773 8 50       25 last unless defined $_[$i+1];
774 8         568 return eval '"'.$_[$i+1].'"';
775             }
776             }
777 13736         43975 return;
778             }
779              
780             do
781             {
782             local $SIG{__WARN__} = sub {0};
783             my $rcfile;
784              
785             $rcfile = $INC{'Lingua//ENG/Inflect.pm'} || '';
786             $rcfile =~ s/Inflect.pm$/.inflectrc/;
787             do $rcfile or die "\nBad .inflectrc file ($rcfile):\n\t$@\n"
788             if $rcfile && -r $rcfile && -s $rcfile;
789              
790             $rcfile = "$ENV{HOME}/.inflectrc" || '';
791             do $rcfile or die "\nBad .inflectrc file ($rcfile):\n\t$@\n"
792             if $rcfile && -r $rcfile && -s $rcfile;
793             };
794              
795             sub postprocess # FIX PEDANTRY AND CAPITALIZATION :-)
796             {
797 10285     10285 0 29189 my ($orig, $inflected) = @_;
798 10285 100       25251 $inflected =~ s/([^|]+)\|(.+)/ $classical{all}?$2:$1 /e;
  173         838  
799 10285 100       68322 return $orig =~ /^I$/ ? $inflected
    100          
    100          
800             : $orig =~ /^[A-Z]+$/ ? uc $inflected
801             : $orig =~ /^[A-Z]/ ? ucfirst $inflected
802             : $inflected;
803             }
804              
805             sub PL
806             # PL($word,$number)
807             {
808 1704     1704 1 7596 my ($str, $count) = @_;
809 1704         10332 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
810 1704 50       5365 return $str unless $word;
811 1704   66     4466 my $plural = postprocess $word, _PL_special_adjective($word,$count)
812             || _PL_special_verb($word,$count)
813             || _PL_noun($word,$count);
814 1704         8264 return $pre.$plural.$post;
815             }
816              
817             sub PL_N
818             # PL_N($word,$number)
819             {
820 6553     6553 1 3610683 my ($str, $count) = @_;
821 6553         44211 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
822 6553 50       20502 return $str unless $word;
823 6553         18259 my $plural = postprocess $word, _PL_noun($word,$count);
824 6553         34505 return $pre.$plural.$post;
825             }
826              
827             sub PL_V
828             # PL_V($word,$number)
829             {
830 2028     2028 1 9579 my ($str, $count) = @_;
831 2028         16432 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
832 2028 50       5208 return $str unless $word;
833 2028   66     6722 my $plural = postprocess $word, _PL_special_verb($word,$count)
834             || _PL_general_verb($word,$count);
835 2028         8845 return $pre.$plural.$post;
836             }
837              
838             sub PL_ADJ
839             # PL_ADJ($word,$number)
840             {
841 0     0 1 0 my ($str, $count) = @_;
842 0         0 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
843 0 0       0 return $str unless $word;
844 0   0     0 my $plural = postprocess $word, _PL_special_adjective($word,$count)
845             || $word;
846 0         0 return $pre.$plural.$post;
847             }
848              
849 3384 100 100 3384 0 28446 sub PL_eq { _PL_eq(@_, \&PL_N) || _PL_eq(@_, \&PL_V) || \&PL_ADJ; }
850 0     0 0 0 sub PL_N_eq { _PL_eq(@_, \&PL_N); }
851 0     0 0 0 sub PL_V_eq { _PL_eq(@_, \&PL_V); }
852 0     0 0 0 sub PL_ADJ_eq { _PL_eq(@_, \&PL_ADJ); }
853              
854             sub _PL_eq
855             {
856 3504     3504   8775 my ( $word1, $word2, $PL ) = @_;
857 3504         15335 my %classval = %classical;
858 3504         18054 %classical = %all_classical;
859 3504         7353 my $result = "";
860 3504 100 66     13990 $result = "eq" if !$result && $word1 eq $word2;
861 3504 100 100     10066 $result = "p:s" if !$result && $word1 eq &$PL($word2);
862 3504 100 100     10208 $result = "s:p" if !$result && &$PL($word1) eq $word2;
863 3504         10395 %classical = ();
864 3504 100 100     8818 $result = "p:s" if !$result && $word1 eq &$PL($word2);
865 3504 100 100     8207 $result = "s:p" if !$result && &$PL($word1) eq $word2;
866 3504         15669 %classical = %classval;
867              
868 3504 100 66     19322 if ($PL == \&PL || $PL == \&PL_N)
869             {
870 3384 50 66     12389 $result = "p:p"
871             if !$result && _PL_check_plurals_N($word1,$word2);
872 3384 50 66     8177 $result = "p:p"
873             if !$result && _PL_check_plurals_N($word2,$word1);
874             }
875 3504 50 33     15863 if ($PL == \&PL || $PL == \&PL_ADJ)
876             {
877 0 0 0     0 $result = "p:p"
878             if !$result && _PL_check_plurals_ADJ($word1,$word2,$PL);
879             }
880              
881 3504         19086 return $result;
882             }
883              
884             sub _PL_reg_plurals
885             {
886 3840     3840   295618 $_[0] =~ /($_[1])($_[2]\|\1$_[3]|$_[3]\|\1$_[2])/
887             }
888              
889             sub _PL_check_plurals_N
890             {
891 240     240   530 my $pair = "$_[0]|$_[1]";
892 240 50       883 foreach ( values %PL_sb_irregular_s ) { return 1 if $_ eq $pair; }
  1920         4354  
893 240 50       1215 foreach ( values %PL_sb_irregular ) { return 1 if $_ eq $pair; }
  10320         22760  
894              
895 240 50 33     625 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        
896             || _PL_reg_plurals($pair, $PL_sb_C_is_ides, "is","ides")
897             || _PL_reg_plurals($pair, $PL_sb_C_a_ae, "s","e")
898             || _PL_reg_plurals($pair, $PL_sb_C_en_ina, "ens","ina")
899             || _PL_reg_plurals($pair, $PL_sb_C_um_a, "ums","a")
900             || _PL_reg_plurals($pair, $PL_sb_C_us_i, "uses","i")
901             || _PL_reg_plurals($pair, $PL_sb_C_on_a, "ons","a")
902             || _PL_reg_plurals($pair, $PL_sb_C_o_i, "os","i")
903             || _PL_reg_plurals($pair, $PL_sb_C_ex_ices, "exes","ices")
904             || _PL_reg_plurals($pair, $PL_sb_C_ix_ices, "ixes","ices")
905             || _PL_reg_plurals($pair, $PL_sb_C_i, "s","i")
906             || _PL_reg_plurals($pair, $PL_sb_C_im, "s","im")
907              
908             || _PL_reg_plurals($pair, '.*eau', "s","x")
909             || _PL_reg_plurals($pair, '.*ieu', "s","x")
910             || _PL_reg_plurals($pair, '.*tri', "xes","ces")
911             || _PL_reg_plurals($pair, '.{2,}[yia]n', "xes","ges");
912              
913              
914 240         1495 return 0;
915             }
916              
917             sub _PL_check_plurals_ADJ
918             {
919 0     0   0 my ( $word1a, $word2a ) = @_;
920 0         0 my ( $word1b, $word2b ) = @_;
921              
922 0 0       0 $word1a = '' unless $word1a =~ s/'s?$//;
923 0 0       0 $word2a = '' unless $word2a =~ s/'s?$//;
924 0 0       0 $word1b = '' unless $word1b =~ s/s'$//;
925 0 0       0 $word2b = '' unless $word2b =~ s/s'$//;
926              
927 0 0       0 if ($word1a)
928             {
929 0 0 0     0 return 1 if $word2a && ( _PL_check_plurals_N($word1a, $word2a)
      0        
930             || _PL_check_plurals_N($word2a, $word1a) );
931 0 0 0     0 return 1 if $word2b && ( _PL_check_plurals_N($word1a, $word2b)
      0        
932             || _PL_check_plurals_N($word2b, $word1a) );
933             }
934 0 0       0 if ($word1b)
935             {
936 0 0 0     0 return 1 if $word2a && ( _PL_check_plurals_N($word1b, $word2a)
      0        
937             || _PL_check_plurals_N($word2a, $word1b) );
938 0 0 0     0 return 1 if $word2b && ( _PL_check_plurals_N($word1b, $word2b)
      0        
939             || _PL_check_plurals_N($word2b, $word1b) );
940             }
941              
942              
943 0         0 return "";
944             }
945              
946             sub _PL_noun
947             {
948 8256     8256   17388 my ( $word, $count ) = @_;
949 8256         16637 my $value; # UTILITY VARIABLE
950              
951             # DEFAULT TO PLURAL
952              
953 8256 50 66     33714 $count = $persistent_count
954             if !defined($count) && defined($persistent_count);
955              
956             $count = (defined $count and $count=~/^($PL_count_one)$/io
957             or defined $count and $classical{zero}
958 8256 100 66     44398 and $count=~/^($PL_count_zero)$/io)
959             ? 1
960             : 2;
961              
962 8256 100       19309 return $word if $count==1;
963              
964             # HANDLE USER-DEFINED NOUNS
965              
966 8250 100       20518 return $value if defined($value = ud_match($word, @PL_sb_user_defined));
967              
968              
969             # HANDLE EMPTY WORD, SINGULAR COUNT AND UNINFLECTED PLURALS
970              
971 8247 50       19620 $word eq '' and return $word;
972              
973 8247 100       117757 $word =~ /^($PL_sb_uninflected)$/i
974             and return $word;
975              
976 7533 100 100     44237 $classical{herd} and $word =~ /^($PL_sb_uninflected_herd)$/i
977             and return $word;
978              
979              
980             # HANDLE COMPOUNDS ("Governor General", "mother-in-law", "aide-de-camp", ETC.)
981              
982 7491 100 66     68061 $word =~ /^(?:$PL_sb_postfix_adj)$/i
983             and $value = $2
984             and return _PL_noun($1,2)
985             . $value;
986              
987 7433 100 100     52592 $word =~ /^(?:$PL_sb_prep_dual_compound)$/i
988             and $value = [$2,$3]
989             and return _PL_noun($1,2)
990             . $value->[0]
991             . _PL_noun($value->[1]);
992              
993 7425 100 66     64744 $word =~ /^(?:$PL_sb_prep_compound)$/i
994             and $value = $2
995             and return _PL_noun($1,2)
996             . $value;
997              
998             # HANDLE PRONOUNS
999              
1000             $word =~ /^((?:$PL_prep)\s+)($PL_pron_acc)$/i
1001 7303 100       43308 and return $1.$PL_pron_acc{lc($2)};
1002              
1003 7119 100       23319 $value = $PL_pron_nom{lc($word)}
1004             and return $value;
1005              
1006             $word =~ /^($PL_pron_acc)$/i
1007 6989 100       33091 and return $PL_pron_acc{lc($1)};
1008              
1009             # HANDLE ISOLATED IRREGULAR PLURALS
1010              
1011             $word =~ /(.*)\b($PL_sb_irregular)$/i
1012             and return $1
1013 6961 100 33     46367 . ( $PL_sb_irregular{$2} || $PL_sb_irregular{lc $2} );
1014 6629 100       40481 $word =~ /($PL_sb_U_man_mans)$/i
1015             and return "$1s";
1016 6365 100       36917 $word =~ /(\S*)quy$/i
1017             and return "$1quies";
1018 6357 100       23997 $word =~ /(\S*)(person)$/i and return $classical{persons}?"$1persons":"$1people";
    100          
1019              
1020             # HANDLE FAMILIES OF IRREGULAR PLURALS
1021              
1022 6317 100       22027 $word =~ /(.*)man$/i and return "$1men";
1023 6155 100       23781 $word =~ /(.*[ml])ouse$/i and return "$1ice";
1024 6107 100       23486 $word =~ /(.*)goose$/i and return "$1geese";
1025 6099 100       17431 $word =~ /(.*)tooth$/i and return "$1teeth";
1026 6091 100       18849 $word =~ /(.*)foot$/i and return "$1feet";
1027              
1028             # HANDLE UNASSIMILATED IMPORTS
1029              
1030 6081 100       22525 $word =~ /(.*)ceps$/i and return $word;
1031 6077 100       25895 $word =~ /(.*)zoon$/i and return "$1zoa";
1032 6053 100       21403 $word =~ /(.*[csx])is$/i and return "$1es";
1033 6021 100       33005 $word =~ /($PL_sb_U_ch_chs)ch$/i and return "$1chs";
1034 5997 100       26701 $word =~ /($PL_sb_U_ex_ices)ex$/i and return "$1ices";
1035 5973 100       30056 $word =~ /($PL_sb_U_ix_ices)ix$/i and return "$1ices";
1036 5957 100       30045 $word =~ /($PL_sb_U_um_a)um$/i and return "$1a";
1037 5885 100       29957 $word =~ /($PL_sb_U_us_i)us$/i and return "$1i";
1038 5813 100       26812 $word =~ /($PL_sb_U_on_a)on$/i and return "$1a";
1039 5741 100       32752 $word =~ /($PL_sb_U_a_ae)$/i and return "$1e";
1040              
1041             # HANDLE INCOMPLETELY ASSIMILATED IMPORTS
1042              
1043 5709 100       16874 if ($classical{ancient})
1044             {
1045 4060 100       13124 $word =~ /(.*)trix$/i and return "$1trices";
1046 4029 100       12029 $word =~ /(.*)eau$/i and return "$1eaux";
1047 3975 100       11280 $word =~ /(.*)ieu$/i and return "$1ieux";
1048 3963 100       17295 $word =~ /(.{2,}[yia])nx$/i and return "$1nges";
1049 3939 100       23881 $word =~ /($PL_sb_C_en_ina)en$/i and return "$1ina";
1050 3915 100       20508 $word =~ /($PL_sb_C_ex_ices)ex$/i and return "$1ices";
1051 3866 100       14977 $word =~ /($PL_sb_C_ix_ices)ix$/i and return "$1ices";
1052 3860 100       21213 $word =~ /($PL_sb_C_um_a)um$/i and return "$1a";
1053 3691 100       19294 $word =~ /($PL_sb_C_us_i)us$/i and return "$1i";
1054 3607 100       32899 $word =~ /($PL_sb_C_us_us)$/i and return "$1";
1055 3579 100       32117 $word =~ /($PL_sb_C_a_ae)$/i and return "$1e";
1056 3477 100       21272 $word =~ /($PL_sb_C_a_ata)a$/i and return "$1ata";
1057 3345 100       26926 $word =~ /($PL_sb_C_is_ides)is$/i and return "$1ides";
1058 3279 100       20248 $word =~ /($PL_sb_C_o_i)o$/i and return "$1i";
1059 3231 100       13093 $word =~ /($PL_sb_C_on_a)on$/i and return "$1a";
1060 3225 100       16570 $word =~ /$PL_sb_C_im$/i and return "${word}im";
1061 3207 100       14681 $word =~ /$PL_sb_C_i$/i and return "${word}i";
1062             }
1063              
1064              
1065             # HANDLE SINGULAR NOUNS ENDING IN ...s OR OTHER SILIBANTS
1066              
1067 4844 100       47548 $word =~ /^($PL_sb_singular_s)$/i and return "$1es";
1068 3506 50 66     11249 $word =~ /^([A-Z].*s)$/ and $classical{names} and return "$1es";
1069 3446 100       12315 $word =~ /^(.*[^z])(z)$/i and return "$1zzes";
1070 3430 100       20515 $word =~ /^(.*)([cs]h|x|zz|ss)$/i and return "$1$2es";
1071             # $word =~ /(.*)(us)$/i and return "$1$2es";
1072              
1073             # HANDLE ...f -> ...ves
1074              
1075 3201 100       10396 $word =~ /(.*[eao])lf$/i and return "$1lves";
1076 3145 100       16342 $word =~ /(.*[^d])eaf$/i and return "$1eaves";
1077 3117 100       9534 $word =~ /(.*[nlw])ife$/i and return "$1ives";
1078 3073 100       10154 $word =~ /(.*)arf$/i and return "$1arves";
1079              
1080             # HANDLE ...y
1081              
1082 3049 100       9888 $word =~ /(.*[aeiou])y$/i and return "$1ys";
1083 2989 100 100     7897 $word =~ /([A-Z].*y)$/ and $classical{names} and return "$1s";
1084 2972 100       12061 $word =~ /(.*)y$/i and return "$1ies";
1085              
1086             # HANDLE ...o
1087              
1088 2910 100       60375 $word =~ /$PL_sb_U_o_os$/i and return "${word}s";
1089 2638 100       8874 $word =~ /[aeiou]o$/i and return "${word}s";
1090 2566 100       8813 $word =~ /o$/i and return "${word}es";
1091              
1092              
1093             # OTHERWISE JUST ADD ...s
1094              
1095 2478         14904 return "${word}s";
1096             }
1097              
1098              
1099             sub _PL_special_verb
1100             {
1101 3685     3685   7892 my ( $word, $count ) = @_;
1102 3685 50 66     14118 $count = $persistent_count
1103             if !defined($count) && defined($persistent_count);
1104             $count = (defined $count and $count=~/^($PL_count_one)$/io or
1105 3685 50 33     20816 defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1
1106             : 2;
1107              
1108 3685 50       18524 return if $count=~/^($PL_count_one)$/io;
1109              
1110 3685         8477 my $value; # UTILITY VARIABLE
1111              
1112             # HANDLE USER-DEFINED VERBS
1113              
1114 3685 100       8378 return $value if defined($value = ud_match($word, @PL_v_user_defined));
1115              
1116             # HANDLE IRREGULAR PRESENT TENSE (SIMPLE AND COMPOUND)
1117              
1118             $word =~ /^($PL_v_irregular_pres)((\s.*)?)$/i
1119 3683 100       27357 and return $PL_v_irregular_pres{lc $1}.$2;
1120              
1121             # HANDLE IRREGULAR FUTURE, PRETERITE AND PERFECT TENSES
1122              
1123 3555 100       25607 $word =~ /^($PL_v_irregular_non_pres)((\s.*)?)$/i
1124             and return $word;
1125              
1126             # HANDLE PRESENT NEGATIONS (SIMPLE AND COMPOUND)
1127              
1128             $word =~ /^($PL_v_irregular_pres)(n't(\s.*)?)$/i
1129 3495 100       15622 and return $PL_v_irregular_pres{lc $1}.$2;
1130              
1131 3465 100       9328 $word =~ /^\S+n't\b/i
1132             and return $word;
1133              
1134             # HANDLE SPECIAL CASES
1135              
1136 3457 100       48573 $word =~ /^($PL_v_special_s)$/ and return;
1137 2841 100       9005 $word =~ /\s/ and return;
1138              
1139             # HANDLE STANDARD 3RD PERSON (CHOP THE ...(e)s OFF SINGLE WORDS)
1140              
1141 2689 100       14739 $word =~ /^(.*)([cs]h|[x]|zz|ss)es$/i and return "$1$2";
1142              
1143 2673 100       8238 $word =~ /^(..+)ies$/i and return "$1y";
1144              
1145 2664 100       25543 $word =~ /($PL_v_oes_oe)$/ and return substr($1,0,-1);
1146 2568 50       7949 $word =~ /^(.+)oes$/i and return "$1o";
1147              
1148 2568 100       10546 $word =~ /^(.*[^s])s$/i and return $1;
1149              
1150             # OTHERWISE, A REGULAR VERB (HANDLE ELSEWHERE)
1151              
1152 2393         11061 return;
1153             }
1154              
1155             sub _PL_general_verb
1156             {
1157 1654     1654   6204 my ( $word, $count ) = @_;
1158 1654 50 33     6548 $count = $persistent_count
1159             if !defined($count) && defined($persistent_count);
1160             $count = (defined $count and $count=~/^($PL_count_one)$/io or
1161 1654 50 33     8921 defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1
1162             : 2;
1163              
1164 1654 50       6049 return $word if $count=~/^($PL_count_one)$/io;
1165              
1166             # HANDLE AMBIGUOUS PRESENT TENSES (SIMPLE AND COMPOUND)
1167              
1168             $word =~ /^($PL_v_ambiguous_pres)((\s.*)?)$/i
1169 1654 100       9851 and return $PL_v_ambiguous_pres{lc $1}.$2;
1170              
1171             # HANDLE AMBIGUOUS PRETERITE AND PERFECT TENSES
1172              
1173 1650 100       11077 $word =~ /^($PL_v_ambiguous_non_pres)((\s.*)?)$/i
1174             and return $word;
1175              
1176             # OTHERWISE, 1st OR 2ND PERSON IS UNINFLECTED
1177              
1178 1632         8099 return $word;
1179              
1180             }
1181              
1182             sub _PL_special_adjective
1183             {
1184 1704     1704   3398 my ( $word, $count ) = @_;
1185 1704 50 66     7057 $count = $persistent_count
1186             if !defined($count) && defined($persistent_count);
1187             $count = (defined $count and $count=~/^($PL_count_one)$/io or
1188 1704 100 100     8842 defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1
1189             : 2;
1190              
1191 1704 100       6811 return $word if $count=~/^($PL_count_one)$/io;
1192              
1193              
1194             # HANDLE USER-DEFINED ADJECTIVES
1195              
1196 1700         4246 my $value;
1197 1700 100       3991 return $value if defined($value = ud_match($word, @PL_adj_user_defined));
1198              
1199             # HANDLE KNOWN CASES
1200              
1201             $word =~ /^($PL_adj_special)$/i
1202 1697 100       11211 and return $PL_adj_special{lc $1};
1203              
1204             # HANDLE POSSESSIVES
1205              
1206             $word =~ /^($PL_adj_poss)$/i
1207 1687 100       7798 and return $PL_adj_poss{lc $1};
1208              
1209 1671 100       4533 $word =~ /^(.*)'s?$/ and do { my $pl = PL_N($1);
  14         36  
1210 14 100       93 return "$pl'" . ($pl =~ m/s$/ ? "" : "s");
1211             };
1212              
1213             # OTHERWISE, NO IDEA
1214              
1215 1657         6323 return;
1216              
1217             }
1218              
1219              
1220             # 2. INDEFINITE ARTICLES
1221              
1222             # THIS PATTERN MATCHES STRINGS OF CAPITALS STARTING WITH A "VOWEL-SOUND"
1223             # CONSONANT FOLLOWED BY ANOTHER CONSONANT, AND WHICH ARE NOT LIKELY
1224             # TO BE REAL WORDS (OH, ALL RIGHT THEN, IT'S JUST MAGIC!)
1225              
1226             my $A_abbrev = q{
1227             (?! FJO | [HLMNS]Y. | RY[EO] | SQU
1228             | ( F[LR]? | [HL] | MN? | N | RH? | S[CHKLMNPTVW]? | X(YL)?) [AEIOU])
1229             [FHLMNRSX][A-Z]
1230             };
1231              
1232             # THIS PATTERN CODES THE BEGINNINGS OF ALL ENGLISH WORDS BEGINING WITH A
1233             # 'y' FOLLOWED BY A CONSONANT. ANY OTHER Y-CONSONANT PREFIX THEREFORE
1234             # IMPLIES AN ABBREVIATION.
1235              
1236             my $A_y_cons = 'y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)';
1237              
1238             # EXCEPTIONS TO EXCEPTIONS
1239              
1240             my $A_explicit_an = enclose join '|',
1241             (
1242             "euler",
1243             "hour(?!i)", "heir", "honest", "hono",
1244             "[fhlmnx]-?th",
1245             );
1246              
1247             sub A
1248             {
1249 109     109 0 6297 my ($str, $count) = @_;
1250 109         700 my ($pre, $word, $post) = ( $str =~ m/\A(\s*)(?:an?\s+)?(.+?)(\s*)\Z/i );
1251 109 50       281 return $str unless $word;
1252 109         221 my $result = _indef_article($word,$count);
1253 109         372 return $pre.$result.$post;
1254             }
1255              
1256 0     0 0 0 sub AN { goto &A }
1257              
1258             sub _indef_article
1259             {
1260 109     109   231 my ( $word, $count ) = @_;
1261              
1262 109 50 33     444 $count = $persistent_count
1263             if !defined($count) && defined($persistent_count);
1264              
1265 109 50 33     275 return "$count $word"
1266             if defined $count && $count!~/^($PL_count_one)$/io;
1267              
1268             # HANDLE USER-DEFINED VARIANTS
1269              
1270 109         163 my $value;
1271 109 50       283 return $value if defined($value = ud_match($word, @A_a_user_defined));
1272              
1273             # HANDLE SPECIAL CASES
1274              
1275 109 50       4903 $word =~ /^($A_explicit_an)/i and return "an $word";
1276 109 50       2371 $word =~ /^[aefhilmnorsx]$/i and return "an $word";
1277 109 50       270 $word =~ /^[bcdgjkpqtuvwyz]$/i and return "a $word";
1278              
1279              
1280             # HANDLE ABBREVIATIONS
1281              
1282 109 50       510 $word =~ /^($A_abbrev)/ox and return "an $word";
1283 109 50       302 $word =~ /^[aefhilmnorsx][.-]/i and return "an $word";
1284 109 100       284 $word =~ /^[a-z][.-]/i and return "a $word";
1285              
1286             # HANDLE CONSONANTS
1287              
1288 98 100       386 $word =~ /^[^aeiouy]/i and return "a $word";
1289              
1290             # HANDLE SPECIAL VOWEL-FORMS
1291              
1292 45 100       125 $word =~ /^e[uw]/i and return "a $word";
1293 39 100       97 $word =~ /^onc?e\b/i and return "a $word";
1294 37 100       126 $word =~ /^uni([^nmd]|mo)/i and return "a $word";
1295 21 100       3452 $word =~ /^u[bcfhjkqrst][aeiou]/i and return "a $word";
1296              
1297             # HANDLE SPECIAL CAPITALS
1298              
1299 7 100       27 $word =~ /^U[NK][AIEO]?/ and return "a $word";
1300              
1301             # HANDLE VOWELS
1302              
1303 5 50       17 $word =~ /^[aeiou]/i and return "an $word";
1304              
1305             # HANDLE y... (BEFORE CERTAIN CONSONANTS IMPLIES (UNNATURALIZED) "i.." SOUND)
1306              
1307 5 50       148 $word =~ /^($A_y_cons)/io and return "an $word";
1308              
1309             # OTHERWISE, GUESS "a"
1310 5         19 return "a $word";
1311             }
1312              
1313             # 2. TRANSLATE ZERO-QUANTIFIED $word TO "no PL($word)"
1314              
1315             sub NO
1316             {
1317 4     4 0 18 my ($str, $count) = @_;
1318 4         33 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
1319              
1320 4 50 33     18 $count = $persistent_count
1321             if !defined($count) && defined($persistent_count);
1322 4 100       12 $count = 0 unless $count;
1323              
1324 4 100       77 return "$pre$count " . PL($word,$count) . $post
1325             unless $count =~ /^$PL_count_zero$/;
1326 2         14 return "${pre}no ". PL($word,0) . $post ;
1327             }
1328              
1329              
1330             # PARTICIPLES
1331              
1332             sub PART_PRES
1333             {
1334 6     6 0 17 local $_ = PL_V(shift,2);
1335 6 100 33     98 s/ie$/y/
      33        
      66        
      66        
      100        
      66        
1336             or s/ue$/u/
1337             or s/([auy])e$/$1/
1338             or s/ski$/ski/
1339             or s/i$//
1340             or s/([^e])e$/$1/
1341             or m/er$/
1342             or s/([^aeiou][aeiouy]([bdgmnprst]))$/$1$2/;
1343 6         42 return "${_}ing";
1344             }
1345              
1346              
1347              
1348             # NUMERICAL INFLECTIONS
1349              
1350             my %nth =
1351             (
1352             0 => 'th',
1353             1 => 'st',
1354             2 => 'nd',
1355             3 => 'rd',
1356             4 => 'th',
1357             5 => 'th',
1358             6 => 'th',
1359             7 => 'th',
1360             8 => 'th',
1361             9 => 'th',
1362             11 => 'th',
1363             12 => 'th',
1364             13 => 'th',
1365             );
1366              
1367              
1368             my %ordinal;
1369             @ordinal{qw(ty one two three five eight nine twelve )}=
1370             qw(tieth first second third fifth eighth ninth twelfth);
1371              
1372             my $ordinal_suff = join '|', keys %ordinal, "";
1373              
1374             $ordinal{""} = 'th';
1375              
1376             sub ORD($)
1377             {
1378 164     164 0 299 my $num = shift;
1379 164 100       585 if ($num =~ /\d/) {
1380 82   66     683 return $num . ($nth{$num%100} || $nth{$num%10});
1381             }
1382             else {
1383 82         859 $num =~ s/($ordinal_suff)\Z/$ordinal{$1}/;
1384 82         385 return $num;
1385             }
1386             }
1387              
1388              
1389             my %default_args =
1390             (
1391             'group' => 0,
1392             'comma' => ',',
1393             'and' => 'and',
1394             'zero' => 'zero',
1395             'one' => 'one',
1396             'decimal' => 'point',
1397             );
1398              
1399             my @unit = ('',qw(one two three four five six seven eight nine));
1400             my @teen = qw(ten eleven twelve thirteen fourteen
1401             fifteen sixteen seventeen eighteen nineteen);
1402             my @ten = ('','',qw(twenty thirty forty fifty sixty seventy eighty ninety));
1403             my @mill = map { (my $val=$_) =~ s/_/illion/; " $val" }
1404             ('',qw(thousand m_ b_ tr_ quadr_ quint_ sext_ sept_ oct_ non_ dec_));
1405              
1406              
1407 1120   100 1120 0 3801 sub mill { my $ind = $_[0]||0;
1408 1120 50       2813 die "Number out of range\n" if $ind > $#mill;
1409 1120 50       7367 return $ind<@mill ? $mill[$ind] : ' ???illion'; }
1410              
1411 755     755 0 2388 sub unit { return $unit[$_[0]]. mill($_[1]); }
1412              
1413             sub ten
1414             {
1415 626 100 100 626 0 3032 return $ten[$_[0]] . ($_[0]&&$_[1]?'-':'') . $unit[$_[1]] . mill($_[2])
    100          
1416             if $_[0] ne '1';
1417 377   100     2409 return $teen[$_[1]]. $mill[$_[2]||0];
1418             }
1419              
1420             sub hund
1421             {
1422 128 100 100 128 0 517 return unit($_[0]) . " hundred" . ($_[1] || $_[2] ? " $_[4] " : '')
    100          
1423             . ten($_[1],$_[2]) . mill($_[3]) . ', ' if $_[0];
1424 24 100 100     135 return ten($_[1],$_[2]) . mill($_[3]) . ', ' if $_[1] || $_[2];
1425 12         70 return '';
1426             }
1427              
1428              
1429             sub enword
1430             {
1431 1039     1039 0 2983 my ($num,$group,$zero,$one,$comma,$and) = @_;
1432              
1433 1039 100       4306 if ($group==1)
    100          
    100          
    100          
    100          
1434             {
1435 85 100       422 $num =~ s/(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")."$comma " /eg;
  321 100       1440  
1436             }
1437             elsif ($group==2)
1438             {
1439 63 100       255 $num =~ s/(\d)(\d)/ ($1 ? ten($1,$2) : $2 ? " $zero " . unit($2) : " $zero $zero") . "$comma " /eg;
  102 100       359  
1440 63 100       196 $num =~ s/(\d)/ ($1 ? unit($1) :" $zero")."$comma " /e;
  30         104  
1441             }
1442             elsif ($group==3)
1443             {
1444 63 100       225 $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       299  
    100          
    100          
1445 63 50       193 $num =~ s/(\d)(\d)/ ($1 ? ten($1,$2) : $2 ? " $zero " . unit($2) : " $zero $zero") . "$comma " /e;
  23 100       98  
1446 63 100       195 $num =~ s/(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")."$comma " /e;
  23 100       135  
1447             }
1448             elsif ($num+0==0) {
1449 48         102 $num = $zero;
1450             }
1451             elsif ($num+0==1) {
1452 44         84 $num = $one;
1453             }
1454             else {
1455 736         1753 $num =~ s/\A\s*0+//;
1456 736         1323 my $mill = 0;
1457 736         2060 1 while $num =~ s/(\d)(\d)(\d)(?=\D*\Z)/ hund($1,$2,$3,$mill++,$and) /e;
  128         365  
1458 736         2596 $num =~ s/(\d)(\d)(?=\D*\Z)/ ten($1,$2,$mill)."$comma " /e;
  356         945  
1459 736         2640 $num =~ s/(\d)(?=\D*\Z)/ unit($1,$mill) . "$comma "/e;
  343         770  
1460             }
1461 1039         3307 return $num;
1462             }
1463              
1464             sub NUMWORDS
1465             {
1466 1281     1281 0 237434 my $num = shift;
1467              
1468 1281 100 66     4327 if (@_ % 2 and require Carp) {
1469 55         801 die "Missing value in option list (odd number of option args) at"
1470             . join ' line ', (caller)[1,2];
1471             }
1472              
1473 1226         6866 my %arg = ( %default_args, @_ );
1474 1226         3273 my $group = $arg{group};
1475              
1476             # Handle "stylistic" conversions (up to a given threshold)...
1477 1226 100 100     3940 if (exists $arg{threshold} && $num > $arg{threshold}) {
1478 230         708 my ($whole, $frac) = split /[.]/, $num;
1479 230         625 while ($arg{comma}) {
1480 230 100       685 $whole =~ s{ (\d) ( \d{3}(?:,|\z) ) }{$1,$2}xms
1481             or last;
1482             }
1483 230 100       1016 return $frac ? "$whole.$frac" : $whole;
1484             }
1485              
1486 996 50       3713 die "Bad chunking option: $group\n" unless $group =~ /\A[0-3]\Z/;
1487 996 50       3485 my $sign = ($num =~ /\A\s*\+/) ? "plus"
    50          
1488             : ($num =~ /\A\s*\-/) ? "minus"
1489             : '';
1490              
1491 996         2641 my ($zero, $one) = @arg{'zero','one'};
1492 996         1921 my $comma = $arg{comma};
1493 996         1946 my $and = $arg{'and'};
1494              
1495 996         2646 my $ord = $num =~ s/(st|nd|rd|th)\Z//;
1496             my @chunks = ($arg{decimal})
1497 996 100       3935 ? $group ? split(/\./, $num) : split(/\./, $num, 2)
    50          
1498             : ($num);
1499              
1500 996         1778 my $first = 1;
1501              
1502 996 100       2442 if ($chunks[0] eq '') { $first=0; shift @chunks; }
  6         13  
  6         12  
1503              
1504 996         2134 foreach ( @chunks )
1505             {
1506 1039         2320 s/\D//g;
1507 1039 100       2669 $_ = '0' unless $_;
1508              
1509 1039 100 100     4086 if (!$group && !$first) { $_ = enword($_,1,$zero,$one,$comma,$and) }
  22         56  
1510 1017         2536 else { $_ = enword($_,$group,$zero,$one,$comma,$and) }
1511              
1512 1039         3861 s/, \Z//;
1513 1039         2949 s/\s+,/,/g;
1514 1039 100 100     4112 s/, (\S+)\s+\Z/ $and $1/ if !$group and $first;
1515 1039         4114 s/\s+/ /g;
1516 1039         5973 s/(\A\s|\s\Z)//g;
1517 1039 100       3123 $first = '' if $first;
1518             }
1519              
1520 996         1957 my @numchunks = ();
1521 996 100       2330 if ($first =~ /0/)
1522             {
1523 6         15 unshift @chunks, '';
1524             }
1525             else
1526             {
1527 990         4961 @numchunks = split /\Q$comma /, $chunks[0];
1528             }
1529              
1530 996 100 100     3371 $numchunks[-1] =~ s/($ordinal_suff)\Z/$ordinal{$1}/
1531             if $ord and @numchunks;
1532              
1533 996         2665 foreach (@chunks[1..$#chunks])
1534             {
1535 49         140 push @numchunks, $arg{decimal};
1536 49         282 push @numchunks, split /\Q$comma /;
1537             }
1538              
1539 996 50       2777 if (wantarray)
    100          
1540             {
1541 0 0       0 unshift @numchunks, $sign if $sign;
1542             return @numchunks
1543 0         0 }
1544             elsif ($group)
1545             {
1546 165 50       1737 return ($sign?"$sign ":'') . join ", ", @numchunks;
1547             }
1548             else
1549             {
1550 831 50       2156 $num = ($sign?"$sign ":'') . shift @numchunks;
1551 831         3428 $first = ($num !~ /$arg{decimal}\Z/);
1552 831         1792 foreach ( @numchunks )
1553             {
1554 176 100       870 if (/\A$arg{decimal}\Z/)
    100          
1555             {
1556 19         41 $num .= " $_";
1557 19         40 $first = 0;
1558             }
1559             elsif ($first)
1560             {
1561 70         187 $num .= "$comma $_";
1562             }
1563             else
1564             {
1565 87         191 $num .= " $_";
1566             }
1567             }
1568 831         4839 return $num;
1569             }
1570             }
1571              
1572             # Join words with commas and a trailing 'and' (when appropriate)...
1573              
1574             sub WORDLIST {
1575 20     20 0 378404 my %opt;
1576             my @words;
1577              
1578 20         52 for my $arg (@_) {
1579 61 100       155 if (ref $arg eq 'HASH' ) {
1580 16         33 %opt = (%opt, %{$arg});
  16         71  
1581             }
1582             else {
1583 45         98 push @words, $arg;
1584             }
1585             }
1586              
1587 20 50       64 return "" if @words == 0;
1588 20 100       109 return "$words[0]" if @words == 1;
1589              
1590 15 100       39 my $conj = exists($opt{conj}) ? $opt{conj} : 'and';
1591 15 100       40 if (@words == 2) {
1592 5         47 $conj =~ s/^ (?=[^\W\d_]) | (?<=[^\W\d_]) $/ /gxms;
1593 5         38 return "$words[0]$conj$words[1]";
1594             }
1595              
1596             my $sep = exists $opt{sep} ? $opt{sep}
1597 10 100       63 : grep(/,/, @words) ? q{; }
    50          
1598             : q{, }
1599             ;
1600              
1601             my $final_sep = !exists $opt{final_sep} ? "$sep $conj"
1602 10 100       68 : length($opt{final_sep}) == 0 ? $conj
    100          
1603             : "$opt{final_sep} $conj"
1604             ;
1605 10         61 $final_sep =~ s/\s+/ /gmxs;
1606 10         78 $final_sep =~ s/^ (?=[^\W\d_]) | (?<=[^\W\d_]) $/ /gxms;
1607              
1608 10         99 return join($sep, @words[0,@words-2]) . "$final_sep$words[-1]";
1609             }
1610              
1611              
1612              
1613             1;
1614              
1615             __END__