File Coverage

blib/lib/Lingua/EN/Inflect.pm
Criterion Covered Total %
statement 341 423 80.6
branch 358 470 76.1
condition 144 285 50.5
subroutine 35 48 72.9
pod 9 33 27.2
total 887 1259 70.4


line stmt bran cond sub pod time code
1             package Lingua::EN::Inflect;
2              
3 15     15   98714 use strict;
  15         112  
  15         621  
4 15     15   93 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS @ISA);
  15         28  
  15         1267  
5 15     15   13485 use Env;
  15         40461  
  15         88  
6              
7             require Exporter;
8             @ISA = qw(Exporter);
9              
10             our $VERSION = '1.904';
11              
12             %EXPORT_TAGS =
13             (
14             ALL => [ qw( classical inflect
15             PL PL_N PL_V PL_ADJ NO NUM A AN
16             PL_eq PL_N_eq PL_V_eq PL_ADJ_eq
17             PART_PRES
18             ORD
19             NUMWORDS
20             WORDLIST
21             def_noun def_verb def_adj def_a def_an )],
22              
23             INFLECTIONS => [ qw( classical inflect
24             PL PL_N PL_V PL_ADJ PL_eq
25             NO NUM A AN PART_PRES )],
26              
27             PLURALS => [ qw( classical inflect
28             PL PL_N PL_V PL_ADJ NO NUM
29             PL_eq PL_N_eq PL_V_eq PL_ADJ_eq )],
30              
31             COMPARISONS => [ qw( classical
32             PL_eq PL_N_eq PL_V_eq PL_ADJ_eq )],
33              
34             ARTICLES => [ qw( classical inflect NUM A AN )],
35              
36             NUMERICAL => [ qw( ORD NUMWORDS )],
37              
38             USER_DEFINED => [ qw( def_noun def_verb def_adj def_a def_an )],
39             );
40              
41             Exporter::export_ok_tags(qw( ALL ));
42              
43             # SUPPORT CLASSICAL PLURALIZATIONS
44              
45             my %def_classical = (
46             all => 0,
47             zero => 0,
48             herd => 0,
49             names => 1,
50             persons => 0,
51             ancient => 0,
52             );
53              
54             my %all_classical = (
55             all => 1,
56             zero => 1,
57             herd => 1,
58             names => 1,
59             persons => 1,
60             ancient => 1,
61             noble_and_ancient => 10,
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 1802 100   1802 0 88352 if (!@_) {
72 1         6 %classical = %all_classical;
73 1         3 return;
74             }
75 1801 100 100     4433 if (@_==1 && $_[0] !~ $classical_mode) {
76 5 100       28 %classical = $_[0] ? %all_classical : ();
77 5         12 return;
78             }
79 1796         3786 while (@_) {
80 2687         4564 my $arg = shift;
81 2687 50       13748 if ($arg !~ $classical_mode) {
82 0         0 die "Unknown classical mode ($arg)\n";
83             }
84 2687 100 66     12130 if (@_ && $_[0] !~ $classical_mode) { $classical{$arg} = shift; }
  2681         6715  
85 6         18 else { $classical{$arg} = 1; }
86              
87 2687 100       6916 if ($arg eq 'all') {
88 1785 100       10230 %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 735     735 0 1812 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             "magnum opus" => "magnum opuses|magna opera",
151             "genus" => "genera",
152             "mythos" => "mythoi",
153             "penis" => "penises|penes",
154             "testis" => "testes",
155             "atlas" => "atlases|atlantes",
156             "yes" => "yeses",
157             'editio princeps' => 'editiones principes',
158             'starets' => 'startsy',
159             'staretz' => 'startzy',
160             'cyclops' => 'cyclopses',
161             'tursiops' => 'tursiopses',
162             'triceratops' => 'triceratopses',
163             'protoceratops' => 'protoceratopses',
164             );
165              
166             my %PL_sb_irregular =
167             (
168             "child" => "children",
169             "brother" => "brothers|brethren",
170             "loaf" => "loaves",
171             "hoof" => "hoofs|hooves",
172             "beef" => "beefs|beeves",
173             "thief" => "thiefs|thieves",
174             "money" => "monies",
175             "mongoose" => "mongooses",
176             "ox" => "oxen",
177             "cow" => "cows|kine",
178             "graffito" => "graffiti",
179             "prima donna" => "prima donnas|prime donne",
180             "octopus" => "octopuses|octopodes",
181             "genie" => "genies|genii",
182             "ganglion" => "ganglions|ganglia",
183             "trilby" => "trilbys",
184             "turf" => "turfs|turves",
185             "numen" => "numina",
186             "atman" => "atmas",
187             "occiput" => "occiputs|occipita",
188             'sabretooth' => 'sabretooths',
189             'sabertooth' => 'sabertooths',
190             'lowlife' => 'lowlifes',
191             'flatfoot' => 'flatfoots',
192             'tenderfoot' => 'tenderfoots',
193             'Romany' => 'Romanies',
194             'romany' => 'romanies',
195             'Tornese' => 'Tornesi',
196             'Jerry' => 'Jerrys',
197             'jerry' => 'jerries',
198             'Mary' => 'Marys',
199             'mary' => 'maries',
200             'talouse' => 'talouses',
201             'blouse' => 'blouses',
202             'Rom' => 'Roma',
203             'rom' => 'roma',
204             'carmen' => 'carmina',
205             'cheval' => 'chevaux',
206             'chervonetz' => 'chervontzi',
207             'kuvasz' => 'kuvaszok',
208             'felo' => 'felones',
209             'put-off' => 'put-offs',
210             'set-off' => 'set-offs',
211             'set-out' => 'set-outs',
212             'set-to' => 'set-tos',
213             'brother-german' => 'brothers-german|brethren-german',
214             'studium generale' => 'studia generali',
215              
216             %PL_sb_irregular_s,
217             );
218              
219             my $PL_sb_irregular = enclose join '|', reverse sort keys %PL_sb_irregular;
220              
221             # Z's that don't double
222              
223             my @PL_sb_z_zes =
224             (
225             "batz", "quartz", "topaz", "snooz(?=e)", "kibbutz",
226             );
227             my $PL_sb_z_zes = enclose join '|', @PL_sb_z_zes;
228              
229             # UNCONDITIONAL "..is" -> "..ides"
230              
231             my @PL_sb_U_is_ides =
232             (
233             "aphis",
234             );
235              
236             my $PL_sb_U_is_ides = enclose join "|", map { substr($_,0,-2) } @PL_sb_U_is_ides;
237              
238             # CLASSICAL "..is" -> "..ides"
239              
240             my @PL_sb_C_is_ides =
241             (
242             # GENERAL WORDS...
243              
244             "ephemeris", "iris", "clitoris",
245             "chrysalis", "epididymis",
246              
247             # INFLAMATIONS...
248              
249             ".*itis",
250              
251             );
252              
253             my $PL_sb_C_is_ides = enclose join "|", map { substr($_,0,-2) } @PL_sb_C_is_ides;
254              
255             # UNCONDITIONAL "..a" -> "..ata"
256              
257             my @PL_sb_U_a_ata =
258             (
259             "plasmalemma", "pseudostoma",
260             );
261              
262             my $PL_sb_U_a_ata = enclose join "|", map { substr($_,0,-1) } @PL_sb_U_a_ata;
263              
264             # CLASSICAL "..a" -> "..ata"
265              
266             my @PL_sb_C_a_ata =
267             (
268             "anathema", "bema", "carcinoma", "charisma", "diploma",
269             "dogma", "drama", "edema", "enema", "enigma", "lemma",
270             "lymphoma", "magma", "melisma", "miasma", "oedema",
271             "sarcoma", "schema", "soma", "stigma", "stoma", "trauma",
272             "gumma", "pragma", "bema",
273             );
274              
275             my $PL_sb_C_a_ata = enclose join "|", map { substr($_,0,-1) } @PL_sb_C_a_ata;
276              
277             # UNCONDITIONAL "..a" -> "..ae"
278              
279             my $PL_sb_U_a_ae = enclose join "|",
280             (
281             "alumna", "alga", "vertebra", "persona"
282             );
283              
284             # CLASSICAL "..a" -> "..ae"
285              
286             my $PL_sb_C_a_ae = enclose join "|",
287             (
288             "amoeba", "antenna", "formula", "hyperbola",
289             "medusa", "nebula", "parabola", "abscissa",
290             "hydra", "nova", "lacuna", "aurora", ".*umbra",
291             "flora", "fauna",
292             );
293              
294             # CLASSICAL "..en" -> "..ina"
295              
296             my $PL_sb_C_en_ina = enclose join "|", map { substr($_,0,-2) }
297             (
298             "stamen", "foramen", "lumen"
299             );
300              
301             # UNCONDITIONAL "..um" -> "..a"
302              
303             my $PL_sb_U_um_a = enclose join "|", map { substr($_,0,-2) }
304             (
305             "bacterium", "agendum", "desideratum", "erratum",
306             "stratum", "datum", "ovum", "extremum",
307             "candelabrum", "intermedium", "malum", "Progymnasium",
308             );
309              
310             # CLASSICAL "..um" -> "..a"
311              
312             my $PL_sb_C_um_a = enclose join "|", map { substr($_,0,-2) }
313             (
314             "maximum", "minimum", "momentum", "optimum",
315             "quantum", "cranium", "curriculum", "dictum",
316             "phylum", "aquarium", "compendium", "emporium",
317             "enconium", "gymnasium", "honorarium", "interregnum",
318             "lustrum", "memorandum", "millennium", "rostrum",
319             "spectrum", "speculum", "stadium", "trapezium",
320             "ultimatum", "medium", "vacuum", "velum",
321             "consortium",
322             );
323              
324             # UNCONDITIONAL "..us" -> "i"
325              
326             my $PL_sb_U_us_i = enclose join "|", map { substr($_,0,-2) }
327             (
328             "alumnus", "alveolus", "bacillus", "bronchus",
329             "locus", "nucleus", "stimulus", "meniscus",
330             "sarcophagus", "interradius", "perradius", "triradius",
331             );
332              
333             # CLASSICAL "..us" -> "..i"
334              
335             my $PL_sb_C_us_i = enclose join "|", map { substr($_,0,-2) }
336             (
337             "focus", "radius", "genius",
338             "incubus", "succubus", "nimbus",
339             "fungus", "nucleolus", "stylus",
340             "torus", "umbilicus", "uterus",
341             "hippopotamus", "cactus",
342             );
343              
344             # CLASSICAL "..us" -> "..us" (ASSIMILATED 4TH DECLENSION LATIN NOUNS)
345              
346             my $PL_sb_C_us_us = enclose join "|",
347             (
348             "status", "apparatus", "prospectus", "sinus",
349             "hiatus", "impetus", "plexus",
350             );
351              
352             # UNCONDITIONAL "..on" -> "a"
353              
354             my $PL_sb_U_on_a = enclose join "|", map { substr($_,0,-2) }
355             (
356             "criterion", "perihelion", "aphelion",
357             "phenomenon", "prolegomenon", "noumenon",
358             "organon", "asyndeton", "hyperbaton",
359             "legomenon",
360             );
361              
362             # CLASSICAL "..on" -> "..a"
363              
364             my $PL_sb_C_on_a = enclose join "|", map { substr($_,0,-2) }
365             (
366             "oxymoron",
367             );
368              
369             # CLASSICAL "..o" -> "..i" (BUT NORMALLY -> "..os")
370              
371             my @PL_sb_C_o_i =
372             (
373             "solo", "soprano", "basso", "alto",
374             "contralto", "tempo", "piano", "virtuoso",
375             );
376             my $PL_sb_C_o_i = enclose join "|", map { substr($_,0,-1) } @PL_sb_C_o_i;
377              
378             # ALWAYS "..o" -> "..os"
379              
380             my $PL_sb_U_o_os = enclose join "|",
381             (
382             "^ado", "aficionado", "aggro",
383             "albino", "allegro", "ammo",
384             "Antananarivo", "archipelago", "armadillo",
385             "auto", "avocado", "Bamako",
386             "Barquisimeto", "bimbo", "bingo",
387             "Biro", "bolero", "Bolzano",
388             "bongo", "Boto", "burro",
389             "Cairo", "canto", "cappuccino",
390             "casino", "cello", "Chicago",
391             "Chimango", "cilantro", "cochito",
392             "coco", "Colombo", "Colorado",
393             "commando", "concertino", "contango",
394             "credo", "crescendo", "cyano",
395             "demo", "ditto", "Draco",
396             "dynamo", "embryo", "Esperanto",
397             "espresso", "euro", "falsetto",
398             "Faro", "fiasco", "Filipino",
399             "flamenco", "furioso", "generalissimo",
400             "Gestapo", "ghetto", "gigolo",
401             "gizmo", "Greensboro", "gringo",
402             "Guaiabero", "guano", "gumbo",
403             "gyro", "hairdo", "hippo",
404             "Idaho", "impetigo", "inferno",
405             "info", "intermezzo", "intertrigo",
406             "Iquico", "^ISO", "jumbo",
407             "junto", "Kakapo", "kilo",
408             "Kinkimavo", "Kokako", "Kosovo",
409             "Lesotho", "libero", "libido",
410             "libretto", "lido", "Lilo",
411             "limbo", "limo", "lineno",
412             "lingo", "lino", "livedo",
413             "loco", "logo", "lumbago",
414             "macho", "macro", "mafioso",
415             "magneto", "magnifico", "Majuro",
416             "Malabo", "manifesto", "Maputo",
417             "Maracaibo", "medico", "memo",
418             "metro", "Mexico", "micro",
419             "Milano", "Monaco", "mono",
420             "Montenegro", "Morocco", "Muqdisho",
421             "myo", "^NATO", "^NCO",
422             "neutrino", "^NGO", "Ningbo",
423             "octavo", "oregano", "Orinoco",
424             "Orlando", "Oslo", "^oto",
425             "panto", "Paramaribo", "Pardusco",
426             "pedalo", "photo", "pimento",
427             "pinto", "pleco", "Pluto",
428             "pogo", "polo", "poncho",
429             "Porto-Novo", "Porto", "pro",
430             "psycho", "pueblo", "quarto",
431             "Quito", "rhino", "risotto",
432             "rococo", "rondo", "Sacramento",
433             "saddo", "sago", "salvo",
434             "Santiago", "Sapporo", "Sarajevo",
435             "scherzando", "scherzo", "silo",
436             "sirocco", "sombrero", "staccato",
437             "sterno", "stucco", "stylo",
438             "sumo", "Taiko", "techno",
439             "terrazzo", "testudo", "timpano",
440             "tiro", "tobacco", "Togo",
441             "Tokyo", "torero", "Torino",
442             "Toronto", "torso", "tremolo",
443             "typo", "tyro", "ufo",
444             "UNESCO", "vaquero", "vermicello",
445             "verso", "vibrato", "violoncello",
446             "Virgo", "weirdo", "WHO",
447             "WTO", "Yamoussoukro", "yo-yo",
448             "zero", "Zibo",
449              
450             @PL_sb_C_o_i,
451             );
452              
453              
454             # UNCONDITIONAL "..ch" -> "..chs"
455              
456             my $PL_sb_U_ch_chs = enclose join "|", map { substr($_,0,-2) }
457             qw(
458             czech eunuch stomach
459             );
460              
461             # UNCONDITIONAL "..[ei]x" -> "..ices"
462              
463             my $PL_sb_U_ex_ices = enclose join "|", map { substr($_,0,-2) }
464             (
465             "codex", "murex", "silex",
466             );
467              
468             my $PL_sb_U_ix_ices = enclose join "|", map { substr($_,0,-2) }
469             (
470             "radix", "helix",
471             );
472              
473             # CLASSICAL "..[ei]x" -> "..ices"
474              
475             my $PL_sb_C_ex_ices = enclose join "|", map { substr($_,0,-2) }
476             (
477             "vortex", "vertex", "cortex", "latex",
478             "pontifex", "apex", "index", "simplex",
479             );
480              
481             my $PL_sb_C_ix_ices = enclose join "|", map { substr($_,0,-2) }
482             (
483             "appendix",
484             );
485              
486             # ARABIC: ".." -> "..i"
487              
488             my $PL_sb_C_i = enclose join "|",
489             (
490             "afrit", "afreet", "efreet",
491             );
492              
493             # HEBREW: ".." -> "..im"
494              
495             my $PL_sb_C_im = enclose join "|",
496             (
497             "goy", "seraph", "cherub", "zuz", "kibbutz",
498             );
499              
500             # UNCONDITIONAL "..man" -> "..mans"
501              
502             my $PL_sb_U_man_mans = enclose join "|",
503             qw(
504             \bataman caiman cayman ceriman
505             \bdesman \bdolman \bfarman \bharman \bhetman
506             human \bleman ottoman shaman talisman
507             Alabaman Bahaman Burman German
508             Hiroshiman Liman Nakayaman Norman Oklahoman
509             Panaman Roman Selman Sonaman Tacoman Yakiman
510             Yokohaman Yuman
511             );
512              
513             my @PL_sb_uninflected_s =
514             (
515             # PAIRS OR GROUPS SUBSUMED TO A SINGULAR...
516             "breeches", "britches", "pajamas", "pyjamas", "clippers", "gallows",
517             "hijinks", "headquarters", "pliers", "scissors", "testes", "herpes",
518             "pincers", "shears", "proceedings", "trousers",
519              
520             # UNASSIMILATED LATIN 4th DECLENSION
521              
522             "cantus", "coitus", "nexus",
523              
524             # RECENT IMPORTS...
525             "contretemps", "corps", "debris",
526             ".*ois", "siemens",
527              
528             # DISEASES
529             ".*measles", "mumps",
530              
531             # MISCELLANEOUS OTHERS...
532             "diabetes", "jackanapes", ".*series", "species", "rabies",
533             "chassis", "innings", "news", "mews", "haggis",
534             );
535              
536             my $PL_sb_uninflected_herd = enclose join "|",
537             # DON'T INFLECT IN CLASSICAL MODE, OTHERWISE NORMAL INFLECTION
538             (
539             "wildebeest", "swine", "eland", "bison", "buffalo",
540             "elk", "rhinoceros", 'zucchini',
541             'caribou', 'dace', 'grouse', 'guinea[- ]fowl',
542             'haddock', 'hake', 'halibut', 'herring', 'mackerel',
543             'pickerel', 'pike', 'roe', 'seed', 'shad',
544             'snipe', 'teal', 'turbot', 'water[- ]fowl',
545             );
546              
547             my $PL_sb_lese_lesen = enclose join "|",
548             (
549             'Auslese',
550             'beerenauslese',
551             'Spaetlese',
552             'trockenbeerenauslese',
553             );
554              
555             my $PL_sb_uninflected = enclose join "|",
556             (
557             # SOME FISH AND HERD ANIMALS
558             ".*fish", "tuna", "salmon", "mackerel", "trout",
559             "bream", "sea[- ]bass", "carp", "cod", "flounder", "whiting",
560              
561             ".*deer", ".*sheep", "moose",
562              
563             # ALL NATIONALS ENDING IN -ese
564             "Portuguese", "Amoyese", "Borghese", "Congoese", "Faroese",
565             "Foochowese", "Genevese", "Genoese", "Gilbertese", "Hottentotese",
566             "Kiplingese", "Kongoese", "Lucchese", "Maltese", "Nankingese",
567             "Niasese", "Pekingese", "Piedmontese", "Pistoiese", "Sarawakese",
568             "Shavese", "Vermontese", "Wenchowese", "Yengeese",
569             ".*[nrlm]ese",
570              
571             # SOME WORDS ENDING IN ...s (OFTEN PAIRS TAKEN AS A WHOLE)
572              
573             @PL_sb_uninflected_s,
574              
575             # DISEASES
576             ".*pox",
577              
578              
579             # OTHER ODDITIES
580             "graffiti", "djinn", 'samuri',
581             '.*craft$', 'offspring', 'pence', 'quid', 'hertz',
582             );
583              
584             # SINGULAR WORDS ENDING IN ...s (ALL INFLECT WITH ...es)
585              
586             my $PL_sb_singular_s = enclose join '|',
587             (
588             ".*ss",
589             "acropolis", "aegis", "alias", "asbestos", "bathos", "bias",
590             "bronchitis", "bursitis", "caddis", "cannabis",
591             "canvas", "chaos", "cosmos", "dais", "digitalis",
592             "epidermis", "ethos", "eyas", "gas", "glottis",
593             "hubris", "ibis", "lens", "mantis", "marquis", "metropolis",
594             "pathos", "pelvis", "polis", "rhinoceros",
595             "sassafras", "trellis", ".*us", "[A-Z].*es",
596              
597             @PL_sb_C_is_ides,
598             @PL_sb_U_is_ides,
599             );
600              
601             my $PL_v_special_s = enclose join '|',
602             (
603             $PL_sb_singular_s,
604             @PL_sb_uninflected_s,
605             keys %PL_sb_irregular_s,
606             '(.*[csx])is',
607             '(.*)ceps',
608             '[A-Z].*s',
609             );
610              
611             my %PL_sb_postfix_adj = (
612             'general' => ['(?!major|lieutenant|brigadier|adjutant|.*star)\S+'],
613             'martial' => [qw(court)],
614             );
615              
616             foreach (keys %PL_sb_postfix_adj) {
617             $PL_sb_postfix_adj{$_} = enclose
618             enclose(join('|', @{$PL_sb_postfix_adj{$_}}))
619             . "(?=(?:-|\\s+)$_)";
620             }
621              
622             my $PL_sb_postfix_adj = '(' . join('|', values %PL_sb_postfix_adj) . ')(.*)';
623              
624             my $PL_prep = enclose join '|', qw (
625             about above across after against amid amidst among around as at athwart atop
626             barring before behind below beneath beside besides between betwixt beyond but by
627             circa
628             despite down during
629             except
630             failing for from
631             given
632             in inside into
633             like
634             minus
635             near next
636             of off on onto out outside over
637             pace past per plus pro
638             qua
639             round
640             sans save since
641             than through throughout thru thruout till times to toward towards
642             under underneath unlike until unto up upon
643             versus via vs
644             with within without worth
645             );
646              
647             my $PL_sb_prep_dual_compound = '(.*?)((?:-|\s+)(?:'.$PL_prep.'|d[eua])(?:-|\s+))a(?:-|\s+)(.*)';
648              
649             my $PL_sb_prep_compound = '(.*?)((-|\s+)('.$PL_prep.'|d[eua])((-|\s+)(.*))?)';
650              
651              
652             my %PL_pron_nom =
653             (
654             # NOMINATIVE REFLEXIVE
655              
656             "i" => "we", "myself" => "ourselves",
657             "you" => "you", "yourself" => "yourselves",
658             "she" => "they", "herself" => "themselves",
659             "he" => "they", "himself" => "themselves",
660             "it" => "they", "itself" => "themselves",
661             "they" => "they", "themself" => "themselves",
662              
663             # POSSESSIVE
664              
665             "mine" => "ours",
666             "yours" => "yours",
667             "hers" => "theirs",
668             "his" => "theirs",
669             "its" => "theirs",
670             "theirs" => "theirs",
671             );
672              
673             my %PL_pron_acc =
674             (
675             # ACCUSATIVE REFLEXIVE
676              
677             "me" => "us", "myself" => "ourselves",
678             "you" => "you", "yourself" => "yourselves",
679             "her" => "them", "herself" => "themselves",
680             "him" => "them", "himself" => "themselves",
681             "it" => "them", "itself" => "themselves",
682             "them" => "them", "themself" => "themselves",
683             );
684              
685             my $PL_pron_acc = enclose join '|', keys %PL_pron_acc;
686              
687             my %PL_v_irregular_pres =
688             (
689             # 1st PERS. SING. 2ND PERS. SING. 3RD PERS. SINGULAR
690             # 3RD PERS. (INDET.)
691              
692             "am" => "are", "are" => "are", "is" => "are",
693             "was" => "were", "were" => "were", "was" => "were",
694             "have" => "have", "have" => "have", "has" => "have",
695             "do" => "do", "do" => "do", "does" => "do",
696             );
697              
698             my $PL_v_irregular_pres = enclose join '|', keys %PL_v_irregular_pres;
699              
700             my %PL_v_ambiguous_pres =
701             (
702             # 1st PERS. SING. 2ND PERS. SING. 3RD PERS. SINGULAR
703             # 3RD PERS. (INDET.)
704              
705             "act" => "act", "act" => "act", "acts" => "act",
706             "blame" => "blame", "blame" => "blame", "blames" => "blame",
707             "can" => "can", "can" => "can", "can" => "can",
708             "must" => "must", "must" => "must", "must" => "must",
709             "fly" => "fly", "fly" => "fly", "flies" => "fly",
710             "copy" => "copy", "copy" => "copy", "copies" => "copy",
711             "drink" => "drink", "drink" => "drink", "drinks" => "drink",
712             "fight" => "fight", "fight" => "fight", "fights" => "fight",
713             "fire" => "fire", "fire" => "fire", "fires" => "fire",
714             "like" => "like", "like" => "like", "likes" => "like",
715             "look" => "look", "look" => "look", "looks" => "look",
716             "make" => "make", "make" => "make", "makes" => "make",
717             "reach" => "reach", "reach" => "reach", "reaches" => "reach",
718             "run" => "run", "run" => "run", "runs" => "run",
719             "sink" => "sink", "sink" => "sink", "sinks" => "sink",
720             "sleep" => "sleep", "sleep" => "sleep", "sleeps" => "sleep",
721             "view" => "view", "view" => "view", "views" => "view",
722             );
723              
724             my $PL_v_ambiguous_pres = enclose join '|', keys %PL_v_ambiguous_pres;
725              
726              
727             my $PL_v_irregular_non_pres = enclose join '|',
728             (
729             "did", "had", "ate", "made", "put",
730             "spent", "fought", "sank", "gave", "sought",
731             "shall", "could", "ought", "should",
732             );
733              
734             my $PL_v_ambiguous_non_pres = enclose join '|',
735             (
736             "thought", "saw", "bent", "will", "might", "cut",
737             );
738              
739             # "..oes" -> "..oe" (the rest are "..oes" -> "o")
740              
741             my $PL_v_oes_oe = enclose join "|",
742             qw(
743             .*shoes .*hoes .*toes
744             canoes floes oboes roes throes woes
745             );
746              
747             my $PL_count_zero = enclose join '|',
748             (
749             0, "no", "zero", "nil"
750             );
751              
752             my $PL_count_one = enclose join '|',
753             (
754             1, "a", "an", "one", "each", "every", "this", "that",
755             );
756              
757             my %PL_adj_special =
758             (
759             "a" => "some", "an" => "some",
760             "this" => "these", "that" => "those",
761             );
762             my $PL_adj_special = enclose join '|', keys %PL_adj_special;
763              
764             my %PL_adj_poss =
765             (
766             "my" => "our",
767             "your" => "your",
768             "its" => "their",
769             "her" => "their",
770             "his" => "their",
771             "their" => "their",
772             );
773             my $PL_adj_poss = enclose join '|', keys %PL_adj_poss;
774              
775              
776             sub checkpat
777             {
778 0     0 0 0 local $SIG{__WARN__} = sub {0};
  6     6   33  
779 6 50 33     398 do {$@ =~ s/at.*?$//;
  0         0  
780 0         0 die "\nBad user-defined singular pattern:\n\t$@\n"}
781             if (!eval "'' =~ m/$_[0]/; 1;" or $@);
782 6         31 return @_;
783             }
784              
785             sub checkpatsubs
786             {
787 6     6 0 19 checkpat($_[0]);
788 6 50       15 if (defined $_[1])
789             {
790 6     0   33 local $SIG{__WARN__} = sub {0};
  0         0  
791 6 50 33     299 do {$@ =~ s/at.*?$//;
  0         0  
792 0         0 die "\nBad user-defined plural string: '$_[1]'\n\t$@\n"}
793             if (!eval "qq{$_[1]}; 1;" or $@);
794             }
795 6         29 return @_;
796             }
797              
798             my @PL_sb_user_defined = ();
799             my @PL_v_user_defined = ();
800             my @PL_adj_user_defined = ();
801             my @A_a_user_defined = ();
802              
803             sub def_noun
804             {
805 2     2 1 12 unshift @PL_sb_user_defined, checkpatsubs(@_);
806 2         6 return 1;
807             }
808              
809             sub def_verb
810             {
811 1     1 1 9 unshift @PL_v_user_defined, checkpatsubs(@_[4,5]);
812 1         5 unshift @PL_v_user_defined, checkpatsubs(@_[2,3]);
813 1         4 unshift @PL_v_user_defined, checkpatsubs(@_[0,1]);
814 1         3 return 1;
815             }
816              
817             sub def_adj
818             {
819 1     1 1 6 unshift @PL_adj_user_defined, checkpatsubs(@_);
820 1         3 return 1;
821             }
822              
823             sub def_a
824             {
825 0     0 1 0 unshift @A_a_user_defined, checkpat(@_,'a');
826 0         0 return 1;
827             }
828              
829             sub def_an
830             {
831 0     0 1 0 unshift @A_a_user_defined, checkpat(@_,'an');
832 0         0 return 1;
833             }
834              
835             sub ud_match
836             {
837 14976     14976 0 23059 my $word = shift;
838 14976         33468 for (my $i=0; $i < @_; $i+=2)
839             {
840 44 100       374 if ($word =~ /^(?:$_[$i])$/i)
841             {
842 8 50       22 last unless defined $_[$i+1];
843 8         388 return eval '"'.$_[$i+1].'"';
844             }
845             }
846 14968         31309 return undef;
847             }
848              
849             do
850             {
851             local $SIG{__WARN__} = sub {0};
852             my $rcfile;
853              
854             $rcfile = $INC{'Lingua//EN/Inflect.pm'} || '';
855             $rcfile =~ s/Inflect.pm$/.inflectrc/;
856             do $rcfile or die "\nBad .inflectrc file ($rcfile):\n\t$@\n"
857             if $rcfile && -r $rcfile && -s $rcfile;
858              
859             $rcfile = "$ENV{HOME}/.inflectrc" || '';
860             do $rcfile or die "\nBad .inflectrc file ($rcfile):\n\t$@\n"
861             if $rcfile && -r $rcfile && -s $rcfile;
862             };
863              
864             sub postprocess # FIX PEDANTRY AND CAPITALIZATION :-)
865             {
866 11179     11179 0 24468 my ($orig, $inflected) = @_;
867 11179 100       20854 $inflected =~ s/([^|]+)\|(.+)/ $classical{all}?$2:$1 /e;
  199         742  
868 11179 100       45740 return $orig =~ /^I$/ ? $inflected
    100          
    100          
869             : $orig =~ /^[A-Z]+$/ ? uc $inflected
870             : $orig =~ /^[A-Z]/ ? ucfirst $inflected
871             : $inflected;
872             }
873              
874             sub PL
875             # PL($word,$number)
876             {
877 1914     1914 1 6875 my ($str, $count) = @_;
878 1914         8846 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
879 1914 50       4450 return $str unless $word;
880 1914   66     3868 my $plural = postprocess $word, _PL_special_adjective($word,$count)
881             || _PL_special_verb($word,$count)
882             || _PL_noun($word,$count);
883 1914         6549 return $pre.$plural.$post;
884             }
885              
886             sub PL_N
887             # PL_N($word,$number)
888             {
889 7041     7041 1 17114 my ($str, $count) = @_;
890 7041         35676 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
891 7041 50       16044 return $str unless $word;
892 7041         13312 my $plural = postprocess $word, _PL_noun($word,$count);
893 7041         28448 return $pre.$plural.$post;
894             }
895              
896             sub PL_V
897             # PL_V($word,$number)
898             {
899 2118     2118 1 7240 my ($str, $count) = @_;
900 2118         11122 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
901 2118 50       5166 return $str unless $word;
902 2118   66     4675 my $plural = postprocess $word, _PL_special_verb($word,$count)
903             || _PL_general_verb($word,$count);
904 2118         7064 return $pre.$plural.$post;
905             }
906              
907             sub PL_ADJ
908             # PL_ADJ($word,$number)
909             {
910 106     106 1 233 my ($str, $count) = @_;
911 106         537 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
912 106 50       232 return $str unless $word;
913 106   66     222 my $plural = postprocess $word, _PL_special_adjective($word,$count)
914             || $word;
915 106         449 return $pre.$plural.$post;
916             }
917              
918 3564 100 100 3564 0 20847 sub PL_eq { _PL_eq(@_, \&PL_N) || _PL_eq(@_, \&PL_V) || _PL_eq(@_, \&PL_ADJ); }
919 0     0 0 0 sub PL_N_eq { _PL_eq(@_, \&PL_N); }
920 0     0 0 0 sub PL_V_eq { _PL_eq(@_, \&PL_V); }
921 0     0 0 0 sub PL_ADJ_eq { _PL_eq(@_, \&PL_ADJ); }
922              
923             sub _PL_eq
924             {
925 3744     3744   7315 my ( $word1, $word2, $PL ) = @_;
926 3744         12854 my %classval = %classical;
927 3744         13814 %classical = %all_classical;
928 3744         7146 my $result = "";
929 3744 100 66     12246 $result = "eq" if !$result && $word1 eq $word2;
930 3744 100 100     8826 $result = "p:s" if !$result && $word1 eq &$PL($word2);
931 3744 100 100     9380 $result = "s:p" if !$result && &$PL($word1) eq $word2;
932 3744         8266 %classical = ();
933 3744 100 100     8003 $result = "p:s" if !$result && $word1 eq &$PL($word2);
934 3744 100 100     7549 $result = "s:p" if !$result && &$PL($word1) eq $word2;
935 3744         13336 %classical = %classval;
936              
937 3744 100 66     16130 if ($PL == \&PL || $PL == \&PL_N)
938             {
939 3564 50 66     7501 $result = "p:p"
940             if !$result && _PL_check_plurals_N($word1,$word2);
941 3564 50 66     6896 $result = "p:p"
942             if !$result && _PL_check_plurals_N($word2,$word1);
943             }
944 3744 100 66     12638 if ($PL == \&PL || $PL == \&PL_ADJ)
945             {
946 60 50 33     152 $result = "p:p"
947             if !$result && _PL_check_plurals_ADJ($word1,$word2,$PL);
948             }
949              
950 3744         14947 return $result;
951             }
952              
953             sub _PL_reg_plurals
954             {
955 4320     4320   171926 $_[0] =~ /($_[1])($_[2]\|\1$_[3]|$_[3]\|\1$_[2])/
956             }
957              
958             sub _PL_check_plurals_N
959             {
960 240     240   596 my $pair = "$_[0]|$_[1]";
961 240 50       764 foreach ( values %PL_sb_irregular_s ) { return 1 if $_ eq $pair; }
  3840         6468  
962 240 50       1100 foreach ( values %PL_sb_irregular ) { return 1 if $_ eq $pair; }
  15120         24625  
963              
964 240 50 33     448 return 1 if
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
965             _PL_reg_plurals($pair, $PL_sb_U_a_ata, "as","ata")
966             || _PL_reg_plurals($pair, $PL_sb_C_a_ata, "as","ata")
967             || _PL_reg_plurals($pair, $PL_sb_U_is_ides, "is","ides")
968             || _PL_reg_plurals($pair, $PL_sb_C_is_ides, "is","ides")
969             || _PL_reg_plurals($pair, $PL_sb_C_a_ae, "s","e")
970             || _PL_reg_plurals($pair, $PL_sb_C_en_ina, "ens","ina")
971             || _PL_reg_plurals($pair, $PL_sb_C_um_a, "ums","a")
972             || _PL_reg_plurals($pair, $PL_sb_C_us_i, "uses","i")
973             || _PL_reg_plurals($pair, $PL_sb_C_on_a, "ons","a")
974             || _PL_reg_plurals($pair, $PL_sb_C_o_i, "os","i")
975             || _PL_reg_plurals($pair, $PL_sb_C_ex_ices, "exes","ices")
976             || _PL_reg_plurals($pair, $PL_sb_C_ix_ices, "ixes","ices")
977             || _PL_reg_plurals($pair, $PL_sb_C_i, "s","i")
978             || _PL_reg_plurals($pair, $PL_sb_C_im, "s","im")
979              
980             || _PL_reg_plurals($pair, '.*eau', "s","x")
981             || _PL_reg_plurals($pair, '.*ieu', "s","x")
982             || _PL_reg_plurals($pair, '.*tri', "xes","ces")
983             || _PL_reg_plurals($pair, '.{2,}[yia]n', "xes","ges");
984              
985              
986 240         1190 return 0;
987             }
988              
989             sub _PL_check_plurals_ADJ
990             {
991 0     0   0 my ( $word1a, $word2a ) = @_;
992 0         0 my ( $word1b, $word2b ) = @_;
993              
994 0 0       0 $word1a = '' unless $word1a =~ s/'s?$//;
995 0 0       0 $word2a = '' unless $word2a =~ s/'s?$//;
996 0 0       0 $word1b = '' unless $word1b =~ s/s'$//;
997 0 0       0 $word2b = '' unless $word2b =~ s/s'$//;
998              
999 0 0       0 if ($word1a)
1000             {
1001 0 0 0     0 return 1 if $word2a && ( _PL_check_plurals_N($word1a, $word2a)
      0        
1002             || _PL_check_plurals_N($word2a, $word1a) );
1003 0 0 0     0 return 1 if $word2b && ( _PL_check_plurals_N($word1a, $word2b)
      0        
1004             || _PL_check_plurals_N($word2b, $word1a) );
1005             }
1006 0 0       0 if ($word1b)
1007             {
1008 0 0 0     0 return 1 if $word2a && ( _PL_check_plurals_N($word1b, $word2a)
      0        
1009             || _PL_check_plurals_N($word2a, $word1b) );
1010 0 0 0     0 return 1 if $word2b && ( _PL_check_plurals_N($word1b, $word2b)
      0        
1011             || _PL_check_plurals_N($word2b, $word1b) );
1012             }
1013              
1014              
1015 0         0 return "";
1016             }
1017              
1018             sub _PL_noun
1019             {
1020 8983     8983   16265 my ( $word, $count ) = @_;
1021 8983         12153 my $value; # UTILITY VARIABLE
1022              
1023             # DEFAULT TO PLURAL
1024              
1025 8983 50 66     27350 $count = $persistent_count
1026             if !defined($count) && defined($persistent_count);
1027              
1028             $count = (defined $count and $count=~/^($PL_count_one)$/io
1029             or defined $count and $classical{zero}
1030 8983 100 66     34954 and $count=~/^($PL_count_zero)$/io)
1031             ? 1
1032             : 2;
1033              
1034 8983 100       16038 return $word if $count==1;
1035              
1036             # HANDLE USER-DEFINED NOUNS
1037              
1038 8977 100       16421 return $value if defined($value = ud_match($word, @PL_sb_user_defined));
1039              
1040              
1041             # HANDLE EMPTY WORD, SINGULAR COUNT AND UNINFLECTED PLURALS
1042              
1043 8974 50       16960 $word eq '' and return $word;
1044              
1045 8974 100 100     80814 $word =~ /^($PL_sb_uninflected)$/i && !exists $PL_sb_irregular{$word} && $word !~ /^($PL_sb_lese_lesen)$/i
      100        
1046             and return $word;
1047              
1048 8280 100 100     36794 $classical{herd} and $word =~ /^($PL_sb_uninflected_herd)$/i
1049             and return $word;
1050              
1051              
1052             # HANDLE ISOLATED IRREGULAR PLURALS
1053              
1054             $word =~ /^($PL_sb_irregular)$/i
1055 8238 100 33     36092 and return ( $PL_sb_irregular{$1} || $PL_sb_irregular{lc $1} );
1056             $word =~ /(.*)\b($PL_sb_irregular)$/i
1057 7732 50 0     37003 and return $1 . ( $PL_sb_irregular{$2} || $PL_sb_irregular{lc $2} );
1058              
1059              
1060             # HANDLE COMPOUNDS ("Governor General", "mother-in-law", "aide-de-camp", ETC.)
1061              
1062 7732 100 66     43243 $word =~ /^(?:$PL_sb_postfix_adj)$/i
1063             and $value = $2
1064             and return _PL_noun($1,2)
1065             . $value;
1066              
1067 7672 100 100     39965 $word =~ /^(?:$PL_sb_prep_dual_compound)$/i
1068             and $value = [$2,$3]
1069             and return _PL_noun($1,2)
1070             . $value->[0]
1071             . _PL_noun($value->[1]);
1072              
1073 7664 100 66     46010 $word =~ /^(?:$PL_sb_prep_compound)$/i
1074             and $value = $2
1075             and return _PL_noun($1,2)
1076             . $value;
1077              
1078             # HANDLE PRONOUNS
1079              
1080             $word =~ /^((?:$PL_prep)\s+)($PL_pron_acc)$/i
1081 7512 100       37821 and return $1.$PL_pron_acc{lc($2)};
1082              
1083 7328 100       19575 $value = $PL_pron_nom{lc($word)}
1084             and return $value;
1085              
1086             $word =~ /^($PL_pron_acc)$/i
1087 7198 100       24319 and return $PL_pron_acc{lc($1)};
1088              
1089              
1090             # HANDLE FAMILIES OF IRREGULAR PLURALS
1091              
1092 7170 100       50087 $word =~ /(.*$PL_sb_U_man_mans)$/i
1093             and return "$1s";
1094 6926 100       22752 $word =~ /(\S*)quy$/i
1095             and return "$1quies";
1096 6918 100       16870 $word =~ /(\S*)(person)$/i and return $classical{persons}?"$1persons":"$1people";
    100          
1097 6878 100       16026 $word =~ /(.*)man$/i and return "$1men";
1098 6712 100       15525 $word =~ /(.*[ml])ouse$/i and return "$1ice";
1099 6664 100       13941 $word =~ /(.*)goose$/i and return "$1geese";
1100 6656 100       13045 $word =~ /(.*)tooth$/i and return "$1teeth";
1101 6648 100       13832 $word =~ /(.*)foot$/i and return "$1feet";
1102              
1103             # HANDLE UNASSIMILATED IMPORTS
1104              
1105 6634 100       13855 $word =~ /(.*)ceps$/i and return $word;
1106 6630 100       13330 $word =~ /(.*)zoon$/i and return "$1zoa";
1107 6606 100       15278 $word =~ /(.*[csx])is$/i and return "$1es";
1108 6574 100       19572 $word =~ /(.*$PL_sb_U_a_ata)a$/i and return "$1ata";
1109 6558 100       19107 $word =~ /(.*$PL_sb_U_is_ides)is$/i and return "$1ides";
1110 6550 100       21041 $word =~ /(.*$PL_sb_U_ch_chs)ch$/i and return "$1chs";
1111 6526 100       20114 $word =~ /(.*$PL_sb_U_ex_ices)ex$/i and return "$1ices";
1112 6502 100       19933 $word =~ /(.*$PL_sb_U_ix_ices)ix$/i and return "$1ices";
1113 6478 100       24536 $word =~ /(.*$PL_sb_U_um_a)um$/i and return "$1a";
1114 6358 100       22145 $word =~ /(.*$PL_sb_U_us_i)us$/i and return "$1i";
1115 6246 100       19464 $word =~ /(.*$PL_sb_U_on_a)on$/i and return "$1a";
1116 6150 100       20204 $word =~ /(.*$PL_sb_U_a_ae)$/i and return "$1e";
1117 6118 100       18250 $word =~ /(.*$PL_sb_lese_lesen)$/i and return "$1n";
1118              
1119             # HANDLE INCOMPLETELY ASSIMILATED IMPORTS
1120              
1121 6086 100       12970 if ($classical{ancient})
1122             {
1123 4262 100       9804 $word =~ /(.*)trix$/i and return "$1trices";
1124 4227 100       9252 $word =~ /(.*)eau$/i and return "$1eaux";
1125 4173 100       9650 $word =~ /(.*)ieu$/i and return "$1ieux";
1126 4161 100       12600 $word =~ /(.{2,}[yia])nx$/i and return "$1nges";
1127 4137 100       14049 $word =~ /(.*$PL_sb_C_en_ina)en$/i and return "$1ina";
1128 4119 100       13797 $word =~ /(.*$PL_sb_C_ex_ices)ex$/i and return "$1ices";
1129 4066 100       10220 $word =~ /(.*$PL_sb_C_ix_ices)ix$/i and return "$1ices";
1130 4060 100       15948 $word =~ /(.*$PL_sb_C_um_a)um$/i and return "$1a";
1131 3887 100       13809 $word =~ /(.*$PL_sb_C_us_i)us$/i and return "$1i";
1132 3809 100       12601 $word =~ /(.*$PL_sb_C_us_us)$/i and return "$1";
1133 3781 100       26097 $word =~ /(.*$PL_sb_C_a_ae)$/i and return "$1e";
1134 3673 100       15880 $word =~ /(.*$PL_sb_C_a_ata)a$/i and return "$1ata";
1135 3505 100       17085 $word =~ /(.*$PL_sb_C_is_ides)is$/i and return "$1ides";
1136 3439 100       15619 $word =~ /(.*$PL_sb_C_o_i)o$/i and return "$1i";
1137 3391 100       9182 $word =~ /(.*$PL_sb_C_on_a)on$/i and return "$1a";
1138 3385 100       14859 $word =~ /$PL_sb_C_im$/i and return "${word}im";
1139 3355 100       15717 $word =~ /$PL_sb_C_i$/i and return "${word}i";
1140             }
1141              
1142              
1143             # HANDLE SINGULAR NOUNS ENDING IN ...s OR OTHER SILIBANTS
1144              
1145 5167 100       34539 $word =~ /^($PL_sb_singular_s)$/i and return "$1es";
1146 3796 50 66     9005 $word =~ /^([A-Z].*s)$/ and $classical{names} and return "$1es";
1147 3737 100       11546 $word =~ /^($PL_sb_z_zes)$/i and return "$1es";
1148 3725 100       11640 $word =~ /^(.*[^z])(z)$/i and return "$1zzes";
1149 3701 100       14864 $word =~ /^(.*)([cs]h|x|zz|ss)$/i and return "$1$2es";
1150             # $word =~ /(.*)(us)$/i and return "$1$2es";
1151              
1152             # HANDLE ...f -> ...ves
1153              
1154 3480 100       8477 $word =~ /(.*[eao])lf$/i and return "$1lves";
1155 3440 100       8971 $word =~ /(.*[^d])eaf$/i and return "$1eaves";
1156 3412 100       7686 $word =~ /(.*[nlw])ife$/i and return "$1ives";
1157 3368 100       7314 $word =~ /(.*)arf$/i and return "$1arves";
1158              
1159             # HANDLE ...y
1160              
1161 3344 100       8113 $word =~ /(.*[aeiou])y$/i and return "$1ys";
1162 3284 100 100     7157 $word =~ /([A-Z].*y)$/ and $classical{names} and return "$1s";
1163 3261 100       7481 $word =~ /(.*)y$/i and return "$1ies";
1164              
1165             # HANDLE ...o
1166              
1167 3159 100       46293 $word =~ /$PL_sb_U_o_os$/i and return "${word}s";
1168 2887 100       7899 $word =~ /[aeiou]o$/i and return "${word}s";
1169 2815 100       6473 $word =~ /o$/i and return "${word}es";
1170              
1171              
1172             # OTHERWISE JUST ADD ...s
1173              
1174 2727         15346 return "${word}s";
1175             }
1176              
1177              
1178             sub _PL_special_verb
1179             {
1180 3984     3984   7232 my ( $word, $count ) = @_;
1181 3984 50 66     12901 $count = $persistent_count
1182             if !defined($count) && defined($persistent_count);
1183             $count = (defined $count and $count=~/^($PL_count_one)$/io or
1184 3984 50 33     15353 defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1
1185             : 2;
1186              
1187 3984 50       11253 return undef if $count=~/^($PL_count_one)$/io;
1188              
1189 3984         5402 my $value; # UTILITY VARIABLE
1190              
1191             # HANDLE USER-DEFINED VERBS
1192              
1193 3984 100       7158 return $value if defined($value = ud_match($word, @PL_v_user_defined));
1194              
1195             # HANDLE IRREGULAR PRESENT TENSE (SIMPLE AND COMPOUND)
1196              
1197             $word =~ /^($PL_v_irregular_pres)((\s.*)?)$/i
1198 3982 100       17614 and return $PL_v_irregular_pres{lc $1}.$2;
1199              
1200             # HANDLE IRREGULAR FUTURE, PRETERITE AND PERFECT TENSES
1201              
1202 3854 100       14492 $word =~ /^($PL_v_irregular_non_pres)((\s.*)?)$/i
1203             and return $word;
1204              
1205             # HANDLE PRESENT NEGATIONS (SIMPLE AND COMPOUND)
1206              
1207             $word =~ /^($PL_v_irregular_pres)(n't(\s.*)?)$/i
1208 3794 100       12038 and return $PL_v_irregular_pres{lc $1}.$2;
1209              
1210 3764 100       9856 $word =~ /^\S+n't\b/i
1211             and return $word;
1212              
1213             # HANDLE SPECIAL CASES
1214              
1215 3756 100       25660 $word =~ /^($PL_v_special_s)$/ and return undef;
1216 3096 100       8001 $word =~ /\s/ and return undef;
1217 2924 100       5400 $word =~ /^quizzes$/i and return "quiz";
1218              
1219             # HANDLE STANDARD 3RD PERSON (CHOP THE ...(e)s OFF SINGLE WORDS)
1220              
1221 2920 100       11522 $word =~ /^(.*)([cs]h|[x]|zz|ss)es$/i and return "$1$2";
1222              
1223 2904 100       6347 $word =~ /^(..+)ies$/i and return "$1y";
1224              
1225 2895 100       15280 $word =~ /($PL_v_oes_oe)$/ and return substr($1,0,-1);
1226 2799 50       7116 $word =~ /^(.+)oes$/i and return "$1o";
1227              
1228 2799 100       8689 $word =~ /^(.*[^s])s$/i and return $1;
1229              
1230             # OTHERWISE, A REGULAR VERB (HANDLE ELSEWHERE)
1231              
1232 2624         10574 return undef;
1233             }
1234              
1235             sub _PL_general_verb
1236             {
1237 1742     1742   3544 my ( $word, $count ) = @_;
1238 1742 50 33     5604 $count = $persistent_count
1239             if !defined($count) && defined($persistent_count);
1240             $count = (defined $count and $count=~/^($PL_count_one)$/io or
1241 1742 50 33     7135 defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1
1242             : 2;
1243              
1244 1742 50       4796 return $word if $count=~/^($PL_count_one)$/io;
1245              
1246             # HANDLE AMBIGUOUS PRESENT TENSES (SIMPLE AND COMPOUND)
1247              
1248             $word =~ /^($PL_v_ambiguous_pres)((\s.*)?)$/i
1249 1742 100       7895 and return $PL_v_ambiguous_pres{lc $1}.$2;
1250              
1251             # HANDLE AMBIGUOUS PRETERITE AND PERFECT TENSES
1252              
1253 1738 100       5874 $word =~ /^($PL_v_ambiguous_non_pres)((\s.*)?)$/i
1254             and return $word;
1255              
1256             # OTHERWISE, 1st OR 2ND PERSON IS UNINFLECTED
1257              
1258 1720         6039 return $word;
1259              
1260             }
1261              
1262             sub _PL_special_adjective
1263             {
1264 2020     2020   3678 my ( $word, $count ) = @_;
1265 2020 50 66     6686 $count = $persistent_count
1266             if !defined($count) && defined($persistent_count);
1267             $count = (defined $count and $count=~/^($PL_count_one)$/io or
1268 2020 100 100     8380 defined $count and $classical{zero} and $count=~/^($PL_count_zero)$/io) ? 1
1269             : 2;
1270              
1271 2020 100       5557 return $word if $count=~/^($PL_count_one)$/io;
1272              
1273              
1274             # HANDLE USER-DEFINED ADJECTIVES
1275              
1276 2015         2696 my $value;
1277 2015 100       3712 return $value if defined($value = ud_match($word, @PL_adj_user_defined));
1278              
1279             # HANDLE KNOWN CASES
1280              
1281             $word =~ /^($PL_adj_special)$/i
1282 2012 100       8557 and return $PL_adj_special{lc $1};
1283              
1284             # HANDLE POSSESSIVES
1285              
1286             $word =~ /^($PL_adj_poss)$/i
1287 1986 100       7106 and return $PL_adj_poss{lc $1};
1288              
1289 1948 100       4520 $word =~ /^(.*)'s?$/ and do { my $pl = PL_N($1);
  72         150  
1290 72 100       402 return "$pl'" . ($pl =~ m/s$/ ? "" : "s");
1291             };
1292              
1293             # OTHERWISE, NO IDEA
1294              
1295 1876         5856 return undef;
1296              
1297             }
1298              
1299              
1300             # 2. INDEFINITE ARTICLES
1301              
1302             # THIS PATTERN MATCHES STRINGS OF CAPITALS STARTING WITH A "VOWEL-SOUND"
1303             # CONSONANT FOLLOWED BY ANOTHER CONSONANT, AND WHICH ARE NOT LIKELY
1304             # TO BE REAL WORDS (OH, ALL RIGHT THEN, IT'S JUST MAGIC!)
1305              
1306             my $A_abbrev = q{
1307             (?! FJO | [HLMNS]Y. | RY[EO] | SQU
1308             | ( F[LR]? | [HL] | MN? | N | RH? | S[CHKLMNPTVW]? | X(YL)?) [AEIOU])
1309             [FHLMNRSX][A-Z]
1310             };
1311              
1312             # THIS PATTERN CODES THE BEGINNINGS OF ALL ENGLISH WORDS BEGINING WITH A
1313             # 'y' FOLLOWED BY A CONSONANT. ANY OTHER Y-CONSONANT PREFIX THEREFORE
1314             # IMPLIES AN ABBREVIATION.
1315              
1316             my $A_y_cons = 'y(b[lor]|cl[ea]|fere|gg|p[ios]|rou|tt)';
1317              
1318             # EXCEPTIONS TO EXCEPTIONS
1319              
1320             my $A_explicit_an = enclose join '|',
1321             (
1322             "euler",
1323             "hour(?!i)", "heir", "honest", "hono",
1324             );
1325              
1326             my $A_ordinal_an = enclose join '|',
1327             (
1328             "[aefhilmnorsx]-?th",
1329             );
1330              
1331             my $A_ordinal_a = enclose join '|',
1332             (
1333             "[bcdgjkpqtuvwyz]-?th",
1334             );
1335              
1336             sub A {
1337 0     0 0 0 my ($str, $count) = @_;
1338 0         0 my ($pre, $word, $post) = ( $str =~ m/\A(\s*)(?:an?\s+)?(.+?)(\s*)\Z/i );
1339 0 0       0 return $str unless $word;
1340 0         0 my $result = _indef_article($word,$count);
1341 0         0 return $pre.$result.$post;
1342             }
1343              
1344 0     0 0 0 sub AN { goto &A }
1345              
1346             sub _indef_article {
1347 0     0   0 my ( $word, $count ) = @_;
1348              
1349 0 0 0     0 $count = $persistent_count
1350             if !defined($count) && defined($persistent_count);
1351              
1352 0 0 0     0 return "$count $word"
1353             if defined $count && $count!~/^($PL_count_one)$/io;
1354              
1355             # HANDLE USER-DEFINED VARIANTS
1356              
1357 0         0 my $value;
1358 0 0       0 return "$value $word"
1359             if defined($value = ud_match($word, @A_a_user_defined));
1360              
1361             # HANDLE ORDINAL FORMS
1362              
1363 0 0       0 $word =~ /^($A_ordinal_a)/i and return "a $word";
1364 0 0       0 $word =~ /^($A_ordinal_an)/i and return "an $word";
1365              
1366             # HANDLE SPECIAL CASES
1367              
1368 0 0       0 $word =~ /^($A_explicit_an)/i and return "an $word";
1369 0 0       0 $word =~ /^[aefhilmnorsx]$/i and return "an $word";
1370 0 0       0 $word =~ /^[bcdgjkpqtuvwyz]$/i and return "a $word";
1371              
1372              
1373             # HANDLE ABBREVIATIONS
1374              
1375 0 0       0 $word =~ /^($A_abbrev)/ox and return "an $word";
1376 0 0       0 $word =~ /^[aefhilmnorsx][.-]/i and return "an $word";
1377 0 0       0 $word =~ /^[a-z][.-]/i and return "a $word";
1378              
1379             # HANDLE CONSONANTS
1380              
1381 0 0       0 $word =~ /^[^aeiouy]/i and return "a $word";
1382              
1383             # HANDLE SPECIAL VOWEL-FORMS
1384              
1385 0 0       0 $word =~ /^e[uw]/i and return "a $word";
1386 0 0       0 $word =~ /^onc?e\b/i and return "a $word";
1387 0 0       0 $word =~ /^uni([^nmd]|mo)/i and return "a $word";
1388 0 0       0 $word =~ /^ut[th]/i and return "an $word";
1389 0 0       0 $word =~ /^u[bcfhjkqrst][aeiou]/i and return "a $word";
1390              
1391             # HANDLE SPECIAL CAPITALS
1392              
1393 0 0       0 $word =~ /^U[NK][AIEO]?/ and return "a $word";
1394              
1395             # HANDLE VOWELS
1396              
1397 0 0       0 $word =~ /^[aeiou]/i and return "an $word";
1398              
1399             # HANDLE y... (BEFORE CERTAIN CONSONANTS IMPLIES (UNNATURALIZED) "i.." SOUND)
1400              
1401 0 0       0 $word =~ /^($A_y_cons)/io and return "an $word";
1402              
1403             # OTHERWISE, GUESS "a"
1404 0         0 return "a $word";
1405             }
1406              
1407             # 2. TRANSLATE ZERO-QUANTIFIED $word TO "no PL($word)"
1408              
1409             sub NO
1410             {
1411 124     124 0 2434 my ($str, $count, $opt_ref) = @_;
1412 124         982 my ($pre, $word, $post) = ($str =~ m/\A(\s*)(.+?)(\s*)\Z/);
1413              
1414 124 50 33     395 $count = $persistent_count
1415             if !defined($count) && defined($persistent_count);
1416 124 100       304 $count = 0 unless $count;
1417              
1418 124 100       655 if ($count =~ /^$PL_count_zero$/) {
1419 3         30 return "${pre}no ". PL($word,0) . $post ;
1420             }
1421              
1422 121         269 $count =~ s{[^\d.e+-]}{}gi;
1423 121         272 1 while $count =~ s{([.].*)[.]}{$1};
1424             $count = defined $opt_ref->{words_below} && $count < $opt_ref->{words_below}
1425 121 100 100     500 ? NUMWORDS($count)
1426             : $count;
1427              
1428 121 100 100     414 if (defined $opt_ref->{comma} || defined $opt_ref->{comma_every}) {
1429 56 100       135 $opt_ref->{comma_every} = 3 if !defined $opt_ref->{comma_every};
1430             $opt_ref->{comma} = ',' if !defined $opt_ref->{comma}
1431 56 100 100     246 || $opt_ref->{comma} =~ /^\d+$/;
1432              
1433 56         102 $count = _commify($count, @{$opt_ref}{'comma','comma_every'});
  56         124  
1434             }
1435              
1436 121         327 return "$pre$count " . PL($word,$count) . $post
1437             }
1438              
1439             sub _commify {
1440 56     56   121 my ($number, $comma, $every) = @_;
1441 56 50       121 return if !defined $comma;
1442 56 50       106 return if !defined $comma;
1443 56         402 $number =~ s{(?:(?<=^)|(?<=^-))(\d\d{$every,})}
  42         111  
1444 42         402 { my $n = $1;
1445 42         144 $n=~s/(?<=.)(?=(?:.{$every})+$)/$comma/g;
1446             $n;
1447 56         143 }e;
1448             return $number;
1449             }
1450              
1451             # PARTICIPLES
1452              
1453             sub PART_PRES
1454 6     6 0 18 {
1455 6 100 33     111 local $_ = PL_V(shift,2);
      33        
      66        
      66        
      66        
      33        
      33        
      66        
      66        
1456             s/ie$/y/
1457             or s/ue$/u/
1458             or s/([auy])e$/$1/
1459             or s/ski$/ski/
1460             or s/[^b]i$//
1461             or s/^(are|were)$/be/
1462             or s/^(had)$/hav/
1463             or s/(hoe)$/$1/
1464             or s/([^e])e$/$1/
1465             or m/er$/
1466 6         33 or s/([^aeiou][aeiouy]([bdgmnprst]))$/$1$2/;
1467             return "${_}ing";
1468             }
1469              
1470              
1471              
1472             # NUMERICAL INFLECTIONS
1473              
1474             my %nth =
1475             (
1476             0 => 'th',
1477             1 => 'st',
1478             2 => 'nd',
1479             3 => 'rd',
1480             4 => 'th',
1481             5 => 'th',
1482             6 => 'th',
1483             7 => 'th',
1484             8 => 'th',
1485             9 => 'th',
1486             11 => 'th',
1487             12 => 'th',
1488             13 => 'th',
1489             );
1490              
1491              
1492             my %ordinal;
1493             @ordinal{qw(ty one two three five eight nine twelve )}=
1494             qw(tieth first second third fifth eighth ninth twelfth);
1495              
1496             my $ordinal_suff = join '|', keys %ordinal, "";
1497              
1498             $ordinal{""} = 'th';
1499              
1500             sub ORD($)
1501 166     166 0 284 {
1502 166 100       664 my $num = shift;
1503 83         177 if ($num =~ /\d/) {
1504 83   66     522 my $tail = substr($num, -3);
1505             return $num . ($nth{$tail%100} || $nth{$tail%10});
1506             }
1507 83         602 else {
1508 83         331 $num =~ s/($ordinal_suff)\Z/$ordinal{$1}/;
1509             return $num;
1510             }
1511             }
1512              
1513              
1514             my %default_args =
1515             (
1516             'group' => 0,
1517             'comma' => ',',
1518             'and' => 'and',
1519             'zero' => 'zero',
1520             'one' => 'one',
1521             'decimal' => 'point',
1522             );
1523              
1524             my @unit = ('',qw(one two three four five six seven eight nine));
1525             my @teen = qw(ten eleven twelve thirteen fourteen
1526             fifteen sixteen seventeen eighteen nineteen);
1527             my @ten = ('','',qw(twenty thirty forty fifty sixty seventy eighty ninety));
1528              
1529             ## From http://en.wikipedia.org/wiki/Names_of_large_numbers
1530             our %millmap = (
1531             #num zeros
1532             '0' => { US => q(), UK => q(), EU => q()},
1533             '3' => { US => q(thousand), UK => q(thousand), EU => q(thousand)},
1534             '6' => { US => q(million), UK => q(million), EU => q(million)},
1535             '9' => { US => q(billion), UK => q(thousand million), EU => q(milliard)},
1536             '12' => { US => q(trillion), UK => q(billion), EU => q(billion)},
1537             '15' => { US => q(quadrillion), UK => q(thousand billion), EU => q(billiard)},
1538             '18' => { US => q(quintillion), UK => q(trillion), EU => q(trillion)},
1539             '21' => { US => q(sextillion), UK => q(thousand trillion), EU => q(trilliard)},
1540             '24' => { US => q(septillion), UK => q(quadrillion), EU => q(quadrillion)},
1541             '27' => { US => q(octillion), UK => q(thousand quadrillion), EU => q(quadrilliard)},
1542             '30' => { US => q(nonillion), UK => q(quintillion), EU => q(quintillion)},
1543             '33' => { US => q(decillion), UK => q(thousand quintillion), EU => q(quintilliard)},
1544             '36' => { US => q(undecillion), UK => q(sextillion), EU => q(sextillion)},
1545             '39' => { US => q(duodecillion), UK => q(thousand sextillion), EU => q(sextilliard)},
1546             '42' => { US => q(tredecillion), UK => q(septillion), EU => q(septillion)},
1547             '45' => { US => q(quattuordecillion), UK => q(thousand septillion), EU => q(septilliard)},
1548             '48' => { US => q(quindecillion), UK => q(octillion), EU => q(octillion)},
1549             '51' => { US => q(sedecillion), UK => q(thousand octillion), EU => q(octilliard)},
1550             '54' => { US => q(septendecillion), UK => q(nonillion), EU => q(nonillion)},
1551             '57' => { US => q(octodecillion), UK => q(thousand nonillion), EU => q(nonilliard)},
1552             '60' => { US => q(novendecillion), UK => q(decillion), EU => q(decillion)},
1553             '63' => { US => q(vigintillion), UK => q(thousand decillion), EU => q(decilliard)},
1554             '66' => { US => q(unvigintillion), UK => q(undecillion), EU => q(undecillion)},
1555             '69' => { US => q(duovigintillion), UK => q(thousand undecillion), EU => q(undecilliard)},
1556             '72' => { US => q(tresvigintillion), UK => q(duodecillion), EU => q(duodecillion)},
1557             '75' => { US => q(quattuorvigintillion), UK => q(thousand duodecillion), EU => q(duodecilliard)},
1558             '78' => { US => q(quinquavigintillion), UK => q(tredecillion), EU => q(tredecillion)},
1559             '81' => { US => q(sesvigintillion), UK => q(thousand tredecillion), EU => q(tredecilliard)},
1560             '84' => { US => q(septemvigintillion), UK => q(quattuordecillion), EU => q(quattuordecillion)},
1561             '87' => { US => q(octovigintillion), UK => q(thousand quattuordecillion), EU => q(quattuordecilliard)},
1562             '90' => { US => q(novemvigintillion), UK => q(quindecillion), EU => q(quindecillion)},
1563             '93' => { US => q(trigintillion), UK => q(thousand quindecillion), EU => q(quindecilliard)},
1564             '96' => { US => q(untrigintillion), UK => q(sedecillion), EU => q(sedecillion)},
1565             '99' => { US => q(duotrigintillion), UK => q(thousand sedecillion), EU => q(sedecilliard)},
1566             '102' => { US => q(trestrigintillion), UK => q(septendecillion), EU => q(septendecillion)},
1567             '105' => { US => q(quattuortrigintillion), UK => q(thousand septendecillion), EU => q(septendecilliard)},
1568             '108' => { US => q(quinquatrigintillion), UK => q(octodecillion), EU => q(octodecillion)},
1569             '111' => { US => q(sestrigintillion), UK => q(thousand octodecillion), EU => q(octodecilliard)},
1570             '114' => { US => q(septentrigintillion), UK => q(novendecillion), EU => q(novendecillion)},
1571             '117' => { US => q(octotrigintillion), UK => q(thousand novendecillion), EU => q(novendecilliard)},
1572             '120' => { US => q(noventrigintillion), UK => q(vigintillion), EU => q(vigintillion)},
1573             '123' => { US => q(quadragintillion), UK => q(thousand vigintillion), EU => q(vigintilliard)},
1574             '153' => { US => q(quinquagintillion), UK => q(thousand quinquavigintillion), EU => q(quinquavigintilliard) },
1575             '183' => { US => q(sexagintillion), UK => q(thousand trigintillion), EU => q(trigintilliard) },
1576             '213' => { US => q(septuagintillion), UK => q(thousand quinquatrigintillion), EU => q(quinquatrigintilliard) },
1577             '243' => { US => q(octogintillion), UK => q(thousand quadragintillion), EU => q(quadragintilliard) },
1578             '273' => { US => q(nonagintillion), UK => q(thousand quinquaquadragintillion), EU => q(quinquaquadragintilliard) },
1579             '303' => { US => q(centillion), UK => q(thousand quinquagintillion), EU => q(quinquagintilliard) },
1580             '306' => { US => q(uncentillion), UK => q(unquinquagintillion), EU => q(unquinquagintillion) },
1581             '309' => { US => q(duocentillion), UK => q(thousand unquinquagintillion), EU => q(unquinquagintilliard) },
1582             '312' => { US => q(trescentillion), UK => q(duoquinquagintillion), EU => q(duoquinquagintillion) },
1583             '333' => { US => q(decicentillion), UK => q(thousand quinquaquinquagintillion), EU => q(quinquaquinquagintilliard) },
1584             '336' => { US => q(undecicentillion), UK => q(sesquinquagintillion), EU => q(sesquinquagintillion) },
1585             '363' => { US => q(viginticentillion), UK => q(thousand sexagintillion), EU => q(sexagintilliard) },
1586             '366' => { US => q(unviginticentillion), UK => q(unsexagintillion), EU => q(unsexagintillion) },
1587             '393' => { US => q(trigintacentillion), UK => q(thousand quinquasexagintillion), EU => q(quinquasexagintilliard) },
1588             '423' => { US => q(quadragintacentillion), UK => q(thousand septuagintillion), EU => q(septuagintilliard) },
1589             '453' => { US => q(quinquagintacentillion), UK => q(thousand quinquaseptuagintillion), EU => q(quinquaseptuagintilliard) },
1590             '483' => { US => q(sexagintacentillion), UK => q(thousand octogintillion), EU => q(octogintilliard) },
1591             '513' => { US => q(septuagintacentillion), UK => q(thousand quinquaoctogintillion), EU => q(quinquaoctogintilliard) },
1592             '543' => { US => q(octogintacentillion), UK => q(thousand nonagintillion), EU => q(nonagintilliard) },
1593             '573' => { US => q(nonagintacentillion), UK => q(thousand quinquanonagintillion), EU => q(quinquanonagintilliard) },
1594             '603' => { US => q(ducentillion), UK => q(thousand centillion), EU => q(centilliard) },
1595             '903' => { US => q(trecentillion), UK => q(thousand quinquagintacentillion), EU => q(quinquagintacentilliard) },
1596             '1203' => { US => q(quadringentillion), UK => q(thousand ducentillion), EU => q(ducentilliard) },
1597             '1503' => { US => q(quingentillion), UK => q(thousand quinquagintaducentillion), EU => q(quinquagintaducentilliard) },
1598             '1803' => { US => q(sescentillion), UK => q(thousand trecentillion), EU => q(trecentilliard) },
1599             '2103' => { US => q(septingentillion), UK => q(thousand quinquagintatrecentillion), EU => q(quinquagintatrecentilliard) },
1600             '2403' => { US => q(octingentillion), UK => q(thousand quadringentillion), EU => q(quadringentilliard) },
1601             '2703' => { US => q(nongentillion), UK => q(thousand quinquagintaquadringentillion), EU => q(quinquagintaquadringentilliard) },
1602             '3003' => { US => q(millinillion), UK => q(thousand quingentillion), EU => q(quingentilliard) },
1603             );
1604              
1605             my $millchoice = 'US';
1606             my @millkeys = sort {$a <=> $b } keys %millmap;
1607             my $maxmill = (sort {$a <=> $b } keys %millmap)[-1];
1608             our $millast = undef;
1609             our @mill;
1610             for my $numZeros ( 0..$maxmill ) {
1611             next if $numZeros%3 != 0;
1612             my $arraySlot = $numZeros/3;
1613             if( defined( $millmap{$numZeros} ) ) {
1614             $mill[$arraySlot] = " $millmap{$numZeros}->{$millchoice}";
1615             $millast = $arraySlot;
1616             } else {
1617             my $missing = $arraySlot - $millast;
1618             $mill[$arraySlot] = $mill[$missing] . $mill[$millast];
1619             }
1620             }
1621 14855   100 14855 0 32080  
1622 14855 50       22207 sub mill { my $ind = $_[0]||0;
1623 14855 50       1064264 die "Number out of range\n" if $ind > $#mill;
1624             return $ind<@mill ? $mill[$ind] : ' ???illion'; }
1625 6785     6785 0 12757  
1626             sub unit { return $unit[$_[0]]. mill($_[1]); }
1627              
1628             sub ten
1629 6041 100 100 6041 0 23834 {
    100          
1630             return $ten[$_[0]] . ($_[0]&&$_[1]?'-':'') . $unit[$_[1]] . mill($_[2])
1631 1087   100     4877 if $_[0] ne '1';
1632             return $teen[$_[1]]. $mill[$_[2]||0];
1633             }
1634              
1635             sub hund
1636 3128 100 100 3128 0 7146 {
    100          
1637             return unit($_[0]) . " hundred" . ($_[1] || $_[2] ? " $_[4] " : '')
1638 324 100 100     1203 . ten($_[1],$_[2]) . mill($_[3]) . ', ' if $_[0];
1639 12         47 return ten($_[1],$_[2]) . mill($_[3]) . ', ' if $_[1] || $_[2];
1640             return '';
1641             }
1642              
1643              
1644             sub enword
1645 1075     1075 0 1893 {
1646             my ($num,$group,$zero,$one,$comma,$and) = @_;
1647 1075 100       2974  
    100          
    100          
    100          
    100          
1648             if ($group==1)
1649 90 100       259 {
  3342 100       7869  
1650             $num =~ s/(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")."$comma " /eg;
1651             }
1652             elsif ($group==2)
1653 64 100       167 {
  1602 100       3202  
1654 64 100       189 $num =~ s/(\d)(\d)/ ($1 ? ten($1,$2) : $2 ? " $zero " . unit($2) : " $zero $zero") . "$comma " /eg;
  30         79  
1655             $num =~ s/(\d)/ ($1 ? unit($1) :" $zero")."$comma " /e;
1656             }
1657             elsif ($group==3)
1658 64 100       144 {
  1055 100       2669  
    100          
    100          
1659 64 50       177 $num =~ s/(\d)(\d)(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")." ".($2 ? ten($2,$3) : $3 ? " $zero " . unit($3) : " $zero $zero") . "$comma " /eg;
  23 100       64  
1660 64 100       172 $num =~ s/(\d)(\d)/ ($1 ? ten($1,$2) : $2 ? " $zero " . unit($2) : " $zero $zero") . "$comma " /e;
  23 100       81  
1661             $num =~ s/(\d)/ ($1==1 ? " $one" : $1 ? unit($1) :" $zero")."$comma " /e;
1662             }
1663 49         64 elsif ($num+0==0) {
1664             $num = $zero;
1665             }
1666 45         65 elsif ($num+0==1) {
1667             $num = $one;
1668             }
1669 763         1162 else {
1670 763         865 $num =~ s/\A\s*0+//;
1671 763         3103 my $mill = 0;
  3128         7297  
1672 763         2175 1 while $num =~ s/(\d)(\d)(\d)(?=\D*\Z)/ hund($1,$2,$3,$mill++,$and) /e;
  371         647  
1673 763         2266 $num =~ s/(\d)(\d)(?=\D*\Z)/ ten($1,$2,$mill)."$comma " /e;
  352         551  
1674             $num =~ s/(\d)(?=\D*\Z)/ unit($1,$mill) . "$comma "/e;
1675 1075         2233 }
1676             return $num;
1677             }
1678              
1679             sub NUMWORDS
1680 1315     1315 0 27347 {
1681             my $num = shift;
1682 1315 100 66     3206  
1683 56         514 if (@_ % 2 and require Carp) {
1684             die "Missing value in option list (odd number of option args) at"
1685             . join ' line ', (caller)[1,2];
1686             }
1687 1259         4967  
1688 1259         2181 my %arg = ( %default_args, @_ );
1689             my $group = $arg{group};
1690              
1691 1259 100 100     2872 # Handle "stylistic" conversions (up to a given threshold)...
1692 230         602 if (exists $arg{threshold} && $num > $arg{threshold}) {
1693 230         687 my ($whole, $frac) = split /[.]/, $num;
1694 230 100       464 while ($arg{comma}) {
1695             $whole =~ s{ (\d) ( \d{3}(?:,|\z) ) }{$1,$2}xms
1696             or last;
1697 230 100       746 }
1698             return $frac ? "$whole.$frac" : $whole;
1699             }
1700 1029 50       3021  
1701 1029 50       2489 die "Bad chunking option: $group\n" unless $group =~ /\A[0-3]\Z/;
    50          
1702             my $sign = ($num =~ /\A\s*\+/) ? "plus"
1703             : ($num =~ /\A\s*\-/) ? "minus"
1704             : '';
1705 1029         1765  
1706 1029         1244 my ($zero, $one) = @arg{'zero','one'};
1707 1029         1445 my $comma = $arg{comma};
1708             my $and = $arg{'and'};
1709 1029         1773  
1710             my $ord = $num =~ s/(st|nd|rd|th)\Z//;
1711 1029 100       2840 my @chunks = ($arg{decimal})
    50          
1712             ? $group ? split(/\./, $num) : split(/\./, $num, 2)
1713             : ($num);
1714 1029         1284  
1715             my $first = 1;
1716 1029 100       1840  
  7         8  
  7         12  
1717             if ($chunks[0] eq '') { $first=0; shift @chunks; }
1718 1029         1443  
1719             foreach ( @chunks )
1720 1075         1613 {
1721 1075 100       1555 s/\D//g;
1722             $_ = '0' unless $_;
1723 1075 100 100     2462  
  26         37  
1724 1049         1667 if (!$group && !$first) { $_ = enword($_,1,$zero,$one,$comma,$and) }
1725             else { $_ = enword($_,$group,$zero,$one,$comma,$and) }
1726 1075         2978  
1727 1075         5993 s/, \Z//;
1728 1075 100 100     3776 s/\s+,/,/g;
1729 1075         9867 s/, (\S+)\s+\Z/ $and $1/ if !$group and $first;
1730 1075         22515 s/\s+/ /g;
1731 1075 100       2273 s/(\A\s|\s\Z)//g;
1732             $first = '' if $first;
1733             }
1734 1029         1461  
1735 1029 100       1555 my @numchunks = ();
1736             if ($first =~ /0/)
1737 7         13 {
1738             unshift @chunks, '';
1739             }
1740             else
1741 1022         4315 {
1742             @numchunks = split /\Q$comma /, $chunks[0];
1743             }
1744 1029 100 100     2307  
1745             $numchunks[-1] =~ s/($ordinal_suff)\Z/$ordinal{$1}/
1746             if $ord and @numchunks;
1747 1029         1858  
1748             foreach (@chunks[1..$#chunks])
1749 53         79 {
1750 53         171 push @numchunks, $arg{decimal};
1751             push @numchunks, split /\Q$comma /;
1752             }
1753 1029 50       1884  
    100          
1754             if (wantarray)
1755 0 0       0 {
1756             unshift @numchunks, $sign if $sign;
1757 0         0 return @numchunks
1758             }
1759             elsif ($group)
1760 168 50       1404 {
1761             return ($sign?"$sign ":'') . join ", ", @numchunks;
1762             }
1763             else
1764 861 50       1373 {
1765 861         2041 $num = ($sign?"$sign ":'') . shift @numchunks;
1766 861         1282 $first = ($num !~ /$arg{decimal}\Z/);
1767             foreach ( @numchunks )
1768 3197 100       6068 {
    100          
1769             if (/\A$arg{decimal}\Z/)
1770 22         37 {
1771 22         34 $num .= " $_";
1772             $first = 0;
1773             }
1774             elsif ($first)
1775 3067         4945 {
1776             $num .= "$comma $_";
1777             }
1778             else
1779 108         172 {
1780             $num .= " $_";
1781             }
1782 861         3195 }
1783             return $num;
1784             }
1785             }
1786              
1787             # Join words with commas and a trailing 'and' (when appropriate)...
1788              
1789 29     29 0 134 sub WORDLIST {
1790             my %opt;
1791             my @words;
1792 29         50  
1793 101 100       157 for my $arg (@_) {
1794 24         37 if (ref $arg eq 'HASH' ) {
  24         76  
1795             %opt = (%opt, %{$arg});
1796             }
1797 77         110 else {
1798             push @words, $arg;
1799             }
1800             }
1801 29 50       63  
1802 29 100       86 return "" if @words == 0;
1803             return "$words[0]" if @words == 1;
1804 24 100       46  
1805 24 100       41 my $conj = exists($opt{conj}) ? $opt{conj} : 'and';
1806 6         32 if (@words == 2) {
1807 6         33 $conj =~ s/^ (?=\S) | (?<=\S) $/ /gxms;
1808             return "$words[0]$conj$words[1]";
1809             }
1810              
1811 18 100       78 my $sep = exists $opt{sep} ? $opt{sep}
    50          
1812             : grep(/,/, @words) ? q{; }
1813             : q{, }
1814             ;
1815              
1816 18 100       55 my $final_sep = !exists $opt{final_sep} ? "$sep $conj"
    100          
1817             : length($opt{final_sep}) == 0 ? $conj
1818             : "$opt{final_sep} $conj"
1819 18         72 ;
1820 18         78 $final_sep =~ s/\s+/ /gmxs;
1821             $final_sep =~ s/^ (?=[^\W\d_]) | (?<=\S) $/ /gxms;
1822 18         127  
1823             return join($sep, @words[0..@words-2]) . "$final_sep$words[-1]";
1824             }
1825              
1826              
1827              
1828             1;
1829              
1830             __END__