File Coverage

blib/lib/Lingua/EN/Inflexion.pm
Criterion Covered Total %
statement 81 91 89.0
branch 33 58 56.9
condition 19 29 65.5
subroutine 15 18 83.3
pod 4 5 80.0
total 152 201 75.6


line stmt bran cond sub pod time code
1             package Lingua::EN::Inflexion;
2 24     24   1175474 use 5.010; use warnings;
  24     24   269  
  24         111  
  24         47  
  24         635  
3 24     24   114 use Carp;
  24         40  
  24         1629  
4              
5             our $VERSION = '0.001006';
6              
7             # Import noun, verb, and adj classes...
8 24     24   6865 use Lingua::EN::Inflexion::Term;
  24         76  
  24         2337  
9              
10             sub import {
11 24     24   249 my (undef, @exports) = @_;
12              
13             # Export interface...
14 24 100       101 @exports = qw< noun verb adj inflect wordlist > if !@exports;
15              
16             # Handle renames...
17 24         48 my %export_name;
18 24 100       700 @exports = map { ref eq 'HASH' ? do { @export_name{keys %$_} = values %$_; keys %$_ } : $_ }
  116         293  
  1         10  
  1         8  
19             @exports;
20              
21 24     24   278 no strict 'refs';
  24         52  
  24         22047  
22 24         59 for my $func (@exports) {
23 118   66     178 *{caller().'::'.($export_name{$func}//$func)} = \&{$func};
  118         273488  
  118         217  
24             }
25             }
26              
27              
28             # Noun constructor...
29             sub noun ($) {
30 20974     20974 1 666868 my ($noun) = @_;
31 20974         63514 return Lingua::EN::Inflexion::Noun->new($noun);
32             }
33              
34             # Verb constructor...
35             sub verb ($) {
36 4043     4043 1 84261 my ($verb) = @_;
37 4043         11200 return Lingua::EN::Inflexion::Verb->new($verb);
38             }
39              
40              
41             # Verb constructor...
42             sub adj ($) {
43 80     80 1 18245 my ($adj) = @_;
44 80         239 return Lingua::EN::Inflexion::Adjective->new($adj);
45             }
46              
47              
48             # Convert a list of words to...a list of words in a single string...
49             sub wordlist {
50 29     29 0 122 my (@words, %opt);
51              
52             # Unpack the argument list...
53 29         39 my $sep = ',';
54 29         66 my $conj = 'and';
55 29         48 for my $arg (@_) {
56 101         132 my $argtype = ref($arg);
57              
58 101 100       159 if ($argtype eq q{}) { push @words, $arg; $sep = ';' if $arg =~ /,/; }
  77 100       100  
  77 50       173  
59 24         87 elsif ($argtype eq q{HASH}) { @opt{keys %$arg} = values %$arg }
60 0         0 else { croak 'Invalid $argtype argument to wordlist' }
61             }
62              
63             # Fill in defaults...
64 29   66     86 $conj = $opt{conj} // $conj;
65 29   33     74 $sep = $opt{sep} // $sep;
66              
67             # Set the Oxford comma...
68 29   66     66 my $oxford = $opt{final_sep} // $sep;
69              
70             # Construct the list phrase...
71 29 100       115 my $list = @words < 3
72             ? join(" $conj ", @words)
73             : join("$sep ", @words[0..$#words-1]) . "$oxford $conj $words[-1]";
74              
75             # Condense any extra whitespace...
76 29         105 $list =~ s/(\s)\s+/$1/g;
77              
78 29         124 return $list;
79             }
80              
81              
82             # All-in-one inflexions...
83             my %word_for_number = (
84             0 => 'zero', 5 => 'five',
85             1 => 'one', 6 => 'six',
86             2 => 'two', 7 => 'seven',
87             3 => 'three', 8 => 'eight',
88             4 => 'four', 9 => 'nine',
89             10 => 'ten',
90             );
91              
92             my $normalize_opts = sub {
93             my ($opts) = @_;
94              
95             if ($opts =~ m{ [[:upper:]] }x) {
96             $opts =~ s{ [[:lower:]] }{}gx;
97             }
98             return lc $opts;
99             };
100              
101             sub inflect($) {
102 13     13 1 5812 my ($string) = @_;
103              
104 13         23 my $inflexion = 'singular';
105              
106             my $transform = {
107             'N' => sub{
108 12     12   27 my ($term, $opts) = @_;
109             carp "Unknown '$_' option to command"
110 12         24 for $opts =~ /([^cps])/;
111              
112 12         27 my $word = noun($term);
113 12 100       42 $word = $word->classical if $opts =~ /c/i;
114              
115 12 50       69 return $opts =~ /p/i ? $word->plural
    50          
116             : $opts =~ /s/i ? $word->singular
117             : $word->$inflexion;
118             },
119              
120 12     12   27 'V' => sub{ return verb(shift)->$inflexion; },
121              
122 0     0   0 'A' => sub{ return adj(shift)->$inflexion; },
123              
124             '#' => sub{
125 13     13   27 my ($count, $opts) = @_;
126 13         29 $opts =~ s{e}{asw}g;
127             carp "Unknown '$_' option to <#:...> command"
128 13         34 for $opts =~ /([^acdefinosw\d])/;
129              
130             # Increment count if requested...
131 13 100       32 if ($opts =~ /i/i) {
132 1         3 $count++;
133             }
134              
135             # Decide which inflexion the count requires...
136             $inflexion
137 13 100 66     101 = $count == 1 || $opts =~ /s/i && $count == 0 || $opts =~ /o/i ? 'singular'
138             : 'plural';
139              
140             # Defer handling of A/AN...
141 13 50 66     41 if ($count == 1 && $opts =~ /a/i) {
142 0         0 return "<#a:>";
143             }
144              
145 13 100       51 my $count_word = $opts =~ /w|o/i ? noun($count) : undef;
146 13 50 66     68 $count_word = $count_word->classical if $count_word && $opts =~ /c/i;
147              
148 13 50       29 my $count_thresh = $opts =~ /w(\d+)/i ? $1 : 11;
149              
150             # Otherwise, interpolate count or its equivalent (deferring fuzzies)...
151 13 50 100     122 return $opts =~ /n|s/i && $count == 0 ? 'no'
    50 66        
    50          
    100          
    50          
    100          
152             : $opts =~ /w/i && $opts =~ /o/i ? $count_word->ordinal($count_thresh)
153             : $opts =~ /w/i ? $count_word->cardinal($count_thresh)
154             : $opts =~ /o/i ? $count_word->ordinal(0)
155             : $opts =~ /f/i ? "<#f:$count>"
156             : $opts =~ /d/ ? q{}
157             : $count;
158             },
159 13         119 };
160              
161             # Inflect markups...
162 13         108 $string =~ s{ (?
163             < (? (?-i: [#NVA] ) ) # FUNC is case-sensitive
164             (? [^:]* ) \s*
165             : \s* (? [^>]+? ) \s*
166             >
167             (? \s* )
168             )
169             }{
170 24     24   8346 my %parsed = %+;
  24         7802  
  24         8201  
  37         588  
171 37         152 my $opts = $normalize_opts->($parsed{OPTS});
172 37   50 0   106 my $func = $transform->{ uc $parsed{FUNC} } // sub{shift};
  0         0  
173 37         75 my $replacement = $func->( $parsed{TERM}, $opts );
174 36 50       312 length $replacement > 0 ? $replacement . $parsed{TWS} : q{}
175             }gexmsi;
176              
177             # Inflect consequent A/AN's...
178 12         29 $string =~ s{ <[#]a:> \s*+ (? \S++) }{ noun($+{next_word})->indefinite }gxe;
  0         0  
179 12         18 $string =~ s{ <[#]a:> \s*+ \Z }{ "a" }xe;
  0         0  
180              
181             # Inflect fuzzies...
182             state $fuzzy = sub {
183 0     0   0 my ($count, $is_postfix) = @_;
184              
185 0 0       0 return $count >= 10 ? 'many'
    0          
    0          
    0          
    0          
    0          
    0          
186             : $count >= 6 ? 'several'
187             : $count >= 3 ? 'a few'
188             : $count == 2 ? 'a couple' . ($is_postfix ? q{} : ' of')
189             : $count == 1 ? 'one'
190             : ($is_postfix ? 'none' : 'no')
191             ;
192 12         24 };
193              
194 12         20 $string =~ s{ <\#f: (? \d++) > (?= \s*+ [[:alpha:]]) }
  0         0  
195 12         18 { $fuzzy->($+{count}) }gxe;
  0         0  
196             $string =~ s{ <\#f: (? \d++) > (?= [^[:alpha:]]*+ \Z) }
197             { $fuzzy->($+{count}, 'postfix') }xe;
198 12         166  
199             # And we're done...
200             return $string;
201             }
202              
203              
204             1; # Magic true value required at end of module
205             __END__