File Coverage

lib/Math/String/Charset/Wordlist.pm
Criterion Covered Total %
statement 145 187 77.5
branch 62 116 53.4
condition 13 31 41.9
subroutine 23 25 92.0
pod 18 18 100.0
total 261 377 69.2


line stmt bran cond sub pod time code
1             #############################################################################
2             # Math/String/Charset/Wordlist.pm -- a dictionary charset for Math/String
3              
4             package Math::String::Charset::Wordlist;
5              
6 1     1   12543 use vars qw($VERSION @ISA);
  1         1  
  1         52  
7 1     1   980 use Math::BigInt;
  1         17468  
  1         4  
8              
9             require 5.008003; # requires this Perl version or later
10             require DynaLoader;
11             require Math::String::Charset;
12 1     1   15875 use strict;
  1         2  
  1         34  
13             @ISA = qw/Math::String::Charset DynaLoader/;
14              
15             $VERSION = 0.09; # Current version of this package
16              
17             bootstrap Math::String::Charset::Wordlist $VERSION;
18              
19 1     1   3 use vars qw/$die_on_error/;
  1         1  
  1         1385  
20             $die_on_error = 1; # set to 0 to not die
21              
22             # following hash values are used:
23             # _clen : length of one character (all chars must have same len unless sep)
24             # _start : contains array of all valid start characters
25             # _end : contains hash (for easier lookup) of all valid end characters
26             # _order : = 1
27             # _type : = 2
28             # _error : error message or ""
29             # _minlen: minimum string length (anything shorter is invalid), default -inf
30             # _maxlen: maximum string length (anything longer is invalid), default +inf
31              
32             # wordlist:
33             # _file : path/filename
34             # _len : count of records (as BigInt)
35             # _len_s: count of records (as scalar)
36             # _scale: input/output scale
37             # _obj : tied object (containing the record-offsets and giving us the records)
38              
39             #############################################################################
40             # private, initialize self
41              
42             sub _strict_check
43             {
44             # a per class check, to be overwritten by subclasses
45 7     7   1851 my ($self,$value) = @_;
46              
47 7   100     20 $self->{_type} ||= 2;
48 7   50     12 $self->{_order} ||= 1;
49              
50 7         8 my $class = ref($self);
51             return $self->{_error} = "Wrong type '$self->{_type}' for $class"
52 7 50       11 if $self->{_type} != 2;
53             return $self->{_error} = "Wrong order'$self->{_order}' for $class"
54 7 50       8 if $self->{_order} != 1;
55 7         14 foreach my $key (keys %$value)
56             {
57 12 50       38 return $self->{_error} = "Illegal parameter '$key' for $class"
58             if $key !~ /^(start|order|type|minlen|maxlen|file|end|scale)$/;
59             }
60             }
61              
62             sub _initialize
63             {
64 7     7   15 my ($self,$value) = @_;
65              
66             # sep char not used yet
67 7         6 $self->{_sep} = $value->{sep}; # separator char
68              
69 7   50     10 $self->{_file} = $value->{file} || ''; # filename and path
70              
71 7 50 33     108 if (!-f $self->{_file} || !-e $self->{_file})
72             {
73 0         0 return $self->{_error} = "Cannot open dictionary '$self->{_file}': $!\n";
74             }
75              
76 7 50       27 die ("Cannot find $self->{_file}: $!") unless -f $self->{_file};
77              
78 7         241 $self->{_obj} = _file($self->{_file});
79              
80 7 50       9 die ("Couldn't read $self->{_file}") unless defined $self->{_obj};
81              
82 7         13 $self->{_len_s} = _records($self->{_obj});
83 7         18 $self->{_len} = Math::BigInt->new( $self->{_len_s} );
84              
85             # only one "char" for now
86 7         211 $self->{_minlen} = 0;
87 7         19 $self->{_maxlen} = 1;
88              
89             return $self->{_error} =
90             "Minlen ($self->{_minlen} must be <= than maxlen ($self->{_maxlen})"
91 7 50       15 if ($self->{_minlen} >= $self->{_maxlen});
92 7         9 $self;
93             }
94              
95             sub offset
96             {
97             # return the offset of the n'th word into the file
98 8     8 1 332 my ($self,$n) = @_;
99              
100 8 100       16 $n = $self->{_len_s} + $n if $n < 0;
101 8         39 _offset($self->{_obj},$n);
102             }
103              
104             sub file
105             {
106             # return the dictionary list file
107 7     7 1 7496 my ($self) = @_;
108              
109 7         18 $self->{_file};
110             }
111              
112             sub is_valid
113             {
114             # check wether a string conforms to the given charset sets
115 1     1 1 44 my $self = shift;
116 1         1 my $str = shift;
117              
118             # print "$str\n";
119 1 50       3 return 0 if !defined $str;
120 1 50 33     3 return 1 if $str eq '' && $self->{_minlen} <= 0;
121              
122 1         3 my $int = Math::BigInt->bzero();
123 1         15 my @chars;
124 1 50       2 if (defined $self->{_sep})
125             {
126 0         0 @chars = split /$self->{_sep}/,$str;
127 0 0       0 shift @chars if $chars[0] eq '';
128 0 0       0 pop @chars if $chars[-1] eq $self->{_sep};
129             }
130             else
131             {
132 1         2 @chars = $str;
133             # not supported yet
134             #my $i = 0; my $len = CORE::length($str); my $clen = $self->{_clen};
135             #while ($i < $len)
136             # {
137             # push @chars, substr($str,$i,$clen); $i += $clen;
138             # }
139             }
140             # length okay?
141 1 50       3 return 0 if scalar @chars < $self->{_minlen};
142 1 50       2 return 0 if scalar @chars > $self->{_maxlen};
143              
144             # further checks for strings longer than 1
145 1         2 foreach my $c (@chars)
146             {
147 1 50       2 return 0 if !defined $self->str2num($c);
148             }
149             # all tests passed
150 1         28 1;
151             }
152              
153             sub start
154             {
155             # this returns all the words (warning, this can eat a lot of memory)
156             # in scalar context, returns length()
157 7     7 1 5 my $self = shift;
158              
159 7 100       17 return $self->{_len} unless wantarray;
160              
161 4         5 my @words = ();
162 4         4 my $OBJ = $self->{_obj};
163 4         10 for (my $i = 0; $i < $self->{_len}; $i++)
164             {
165 20         888 push @words, _record($OBJ,$i);
166             }
167 4         142 @words;
168             }
169              
170             sub end
171             {
172             # this returns all the words (warning, this can eat a lot of memory)
173             # in scalar context, returns length()
174 2     2 1 2 my $self = shift;
175              
176 2         3 $self->start();
177             }
178              
179             sub ones
180             {
181             # this returns all the words (warning, this can eat a lot of memory)
182             # in scalar context, returns length()
183 2     2 1 2 my $self = shift;
184              
185 2         4 $self->start();
186             }
187              
188             sub copy
189             {
190             # for speed reasons, do not make a copy of a charset, but share it instead
191 1     1 1 1219 my ($c,$x);
192 1 50       3 if (@_ > 1)
193             {
194             # if two arguments, the first one is the class to "swallow" subclasses
195 0         0 ($c,$x) = @_;
196             }
197             else
198             {
199 1         1 $x = shift;
200 1         2 $c = ref($x);
201             }
202 1 50       3 return unless ref($x); # only for objects
203              
204 1         2 my $self = {}; bless $self,$c;
  1         1  
205 1         4 foreach my $k (keys %$x)
206             {
207 13 50       70 if (ref($x->{$k}) eq 'SCALAR')
    100          
    100          
    50          
    100          
208             {
209 0         0 $self->{$k} = \${$x->{$k}};
  0         0  
210             }
211             elsif ($k eq '_obj')
212             {
213             # to save memory, don't make a full copy of the record set, just copy
214             # the pointer around
215 1         2 $self->{$k} = $x->{$k};
216             }
217             elsif (ref($x->{$k}) eq 'ARRAY')
218             {
219 1         1 $self->{$k} = [ @{$x->{$k}} ];
  1         3  
220             }
221             elsif (ref($x->{$k}) eq 'HASH')
222             {
223             # only one level deep!
224 0         0 foreach my $h (keys %{$x->{$k}})
  0         0  
225             {
226 0         0 $self->{$k}->{$h} = $x->{$k}->{$h};
227             }
228             }
229             elsif (ref($x->{$k}))
230             {
231 2         1 my $c = ref($x->{$k});
232 2         4 $self->{$k} = $c->new($x->{$k}); # no copy() due to deep rec
233             }
234             else
235             {
236             # simple scalar w/o reference
237 9         7 $self->{$k} = $x->{$k};
238             }
239             }
240 1         2 $self;
241             }
242              
243             sub chars
244             {
245 1     1 1 246 my ($self,$x) = @_;
246              
247             # XXX return always 1 to signal that $x has only one character
248 1         3 1;
249             }
250              
251             sub count
252             {
253 1     1 1 1 my $self = shift;
254              
255 1         3 $self->{_len};
256             }
257              
258             sub length
259             {
260 2     2 1 622 my $self = shift;
261              
262 2         5 $self->{_len};
263             }
264              
265             sub class
266             {
267 0     0 1 0 my $self = shift;
268 0 0       0 my $class = shift; $class = 0 unless defined $class;
  0         0  
269              
270             # class(0) is 0
271 0 0       0 return 0 if $class == 0;
272              
273 0 0       0 return $self->{_len} if $class == 1;
274              
275 0         0 $self->{_len}->copy()->bpow($class);
276             }
277              
278             sub num2str
279             {
280             # convert Math::BigInt/Math::String to string
281             # in list context, return (string,stringlen)
282 66     66 1 1273 my ($self,$x) = @_;
283              
284 66 100       208 $x = new Math::BigInt($x) unless ref $x;
285 66 50       1723 return undef if ($x->sign() !~ /^[+-]$/);
286              
287 66         297 my $l = ''; # $x == 0 as default
288 66         119 my $int = abs($x->numify());
289 66 100       956 if ($int > 0)
290             {
291 60         378 $l = _record($self->{_obj}, $int-1);
292             }
293 66 100       263 wantarray ? ($l,1) : $l;
294             }
295              
296             sub str2num
297             {
298             # convert Math::String to Math::BigInt
299 48     48 1 14154 my ($self,$str) = @_;
300              
301 48 50 33     185 return Math::BigInt->bzero() if !defined $str || $str eq '';
302              
303 48         43 my $OBJ = $self->{_obj};
304              
305             # do a binary search for the string in the array of strings
306 48         36 my $left = 0; my $right = $self->{_len_s} - 1;
  48         43  
307              
308 48         249 my $leftstr = _record($OBJ,$left);
309 48 100       75 return Math::BigInt->new($left+1) if $leftstr eq $str;
310 43         67 my $rightstr = _record($OBJ,$right);
311 43 100       62 return Math::BigInt->new($right+1) if $rightstr eq $str;
312              
313 38         23 my $middle;
314 38         50 while ($right - $left > 1)
315             {
316             # simple middle median computing
317 79         85 $middle = int(($left + $right) / 2);
318              
319             # advanced middle computing:
320 79         76 my $ll = ord(substr($leftstr,0,1));
321 79         50 my $rr = ord(substr($rightstr,0,1));
322 79 50       89 if ($rr - $ll > 1)
323             {
324 79         49 my $mm = ord(substr($str,0,1));
325 79 100       82 $mm++ if $mm == $ll;
326 79 50       79 $mm-- if $mm == $rr;
327              
328             # now make $middle so that :
329             # $mm - $ll $middle - $left
330             # ----------- = ----------------- =>
331             # $rr - $ll $right - $left
332             #
333             # ($mm - $ll) * ($right - $left)
334             # $left + ----------------------------
335             # $rr - $ll
336 79         70 $middle = $left +
337             int(($mm - $ll) * ($right - $left) / ($rr - $ll));
338 79 100       75 $middle++ if $middle == $left;
339 79 50       83 $middle-- if $middle == $right;
340             }
341              
342 79         132 my $middlestr = _record($OBJ,$middle);
343 79 100       178 return Math::BigInt->new($middle+1) if $middlestr eq $str;
344              
345             # so it is neither left, nor right nor middle, so see in which half it
346             # should be
347              
348 41         32 my $cmp = $middlestr cmp $str;
349             # cmp != 0 here
350 41 100       57 if ($cmp < 0)
351             {
352 2         2 $left = $middle; $leftstr = $middlestr;
  2         3  
353             }
354             else
355             {
356 39         25 $right = $middle; $rightstr = $middlestr;
  39         49  
357             }
358             }
359 0 0       0 return if $right - $left == 1; # not found
360 0         0 Math::BigInt->new($middle+1);
361             }
362              
363             sub char
364             {
365             # return nth char from charset
366 4     4 1 4 my $self = shift;
367 4   100     11 my $char = shift || 0;
368              
369 4 100       7 $char = $self->{_len_s} + $char if $char < 0;
370 4         23 _record($self->{_obj},$char);
371             }
372              
373             sub first
374             {
375 5     5 1 343 my $self = shift;
376 5   50     10 my $count = abs(shift || 0);
377              
378 5 50       8 return if $count < $self->{_minlen};
379 5 50 33     18 return if defined $self->{_maxlen} && $count > $self->{_maxlen};
380 5 50       6 return '' if $count == 0;
381              
382 5         74 my $str = _record($self->{_obj},0);
383              
384 5 50       17 return $str if $count == 1;
385              
386 0   0     0 my $s = $self->{_sep} || '';
387 0         0 my $res = '';
388 0         0 for (my $i = 0; $i < $count; $i++)
389             {
390 0         0 $res .= $s . $str;
391             }
392 0         0 $s = quotemeta($s);
393 0 0       0 $res =~ s/^$s// if $s ne ''; # remove first sep
394 0         0 $res;
395             }
396              
397             sub last
398             {
399 2     2 1 2 my $self = shift;
400 2   50     6 my $count = abs(shift || 0);
401              
402 2 50       3 return if $count < $self->{_minlen};
403 2 50 33     14 return if defined $self->{_maxlen} && $count > $self->{_maxlen};
404 2 50       3 return '' if $count == 0;
405              
406 2         17 my $str = _record($self->{_obj},$self->{_len_s}-1);
407 2 50       7 return $str if $count == 1;
408              
409 0         0 my $res = '';
410 0   0     0 my $s = $self->{_sep} || '';
411 0         0 for (my $i = 1; $i <= $count; $i++)
412             {
413 0         0 $res .= $s . $str;
414             }
415 0         0 $s = quotemeta($s);
416 0 0       0 $res =~ s/^$s// if $s ne ''; # remove first sep
417 0         0 $res;
418             }
419              
420             sub next
421             {
422 1     1 1 73 my ($self,$str) = @_;
423              
424 1 50       3 if ($str->{_cache} eq '') # 0 => 1
425             {
426 0 0       0 my $min = $self->{_minlen}; $min = 1 if $min <= 0;
  0         0  
427 0         0 $str->{_cache} = $self->first($min);
428 0         0 return;
429             }
430              
431             # only the rightmost digit is adjusted. If this overflows, we simple
432             # invalidate the cache. The time saved by updating the cache would be to
433             # small to be of use, especially since updating the cache takes more time
434             # then. Also, if the cached isn't used later, we would have spent the
435             # update-time in vain.
436              
437             # extract the current value
438             #$str->{_cache} = _record($self->{_obj}, $str->numify()-1);
439 1         1 $str->{_cache} = undef;
440             }
441              
442             sub prev
443             {
444 1     1 1 291 my ($self,$str) = @_;
445              
446 1 50       3 if ($str->{_cache} eq '') # 0 => -1
447             {
448 0 0       0 my $min = $self->{_minlen}; $min = -1 if $min >= 0;
  0         0  
449 0         0 $str->{_cache} = $self->first($min);
450 0         0 return;
451             }
452              
453             # extract the current value
454             #$str->{_cache} = _record($self->{_obj}, $str->numify()-1);
455 1         2 $str->{_cache} = undef;
456             }
457              
458             sub DELETE
459             {
460 0     0     my $self = shift;
461              
462             # untie and free our record-keeper
463 0 0         _free($self->{_obj}) if $self->{_obj};
464             }
465              
466             __END__