File Coverage

lib/Math/String/Charset/Wordlist.pm
Criterion Covered Total %
statement 12 187 6.4
branch 0 116 0.0
condition 0 31 0.0
subroutine 4 25 16.0
pod n/a
total 16 359 4.4


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   23460 use vars qw($VERSION @ISA);
  1         2  
  1         78  
7 1     1   1757 use Math::BigInt;
  1         24495  
  1         7  
8              
9             require 5.008003; # requires this Perl version or later
10             require DynaLoader;
11             require Math::String::Charset;
12 1     1   16999 use strict;
  1         2  
  1         82  
13             @ISA = qw/Math::String::Charset DynaLoader/;
14              
15             $VERSION = 0.08; # Current version of this package
16              
17             bootstrap Math::String::Charset::Wordlist $VERSION;
18              
19 1     1   6 use vars qw/$die_on_error/;
  1         2  
  1         2037  
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 0     0     my ($self,$value) = @_;
46              
47 0   0       $self->{_type} ||= 2;
48 0   0       $self->{_order} ||= 1;
49              
50 0           my $class = ref($self);
51 0 0         return $self->{_error} = "Wrong type '$self->{_type}' for $class"
52             if $self->{_type} != 2;
53 0 0         return $self->{_error} = "Wrong order'$self->{_order}' for $class"
54             if $self->{_order} != 1;
55 0           foreach my $key (keys %$value)
56             {
57 0 0         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 0     0     my ($self,$value) = @_;
65              
66             # sep char not used yet
67 0           $self->{_sep} = $value->{sep}; # separator char
68              
69 0   0       $self->{_file} = $value->{file} || ''; # filename and path
70            
71 0 0 0       if (!-f $self->{_file} || !-e $self->{_file})
72             {
73 0           return $self->{_error} = "Cannot open dictionary '$self->{_file}': $!\n";
74             }
75              
76 0 0         die ("Cannot find $self->{_file}: $!") unless -f $self->{_file};
77              
78 0           $self->{_obj} = _file($self->{_file});
79              
80 0 0         die ("Couldn't read $self->{_file}") unless defined $self->{_obj};
81            
82 0           $self->{_len_s} = _records($self->{_obj});
83 0           $self->{_len} = Math::BigInt->new( $self->{_len_s} );
84              
85             # only one "char" for now
86 0           $self->{_minlen} = 0;
87 0           $self->{_maxlen} = 1;
88              
89 0 0         return $self->{_error} =
90             "Minlen ($self->{_minlen} must be <= than maxlen ($self->{_maxlen})"
91             if ($self->{_minlen} >= $self->{_maxlen});
92 0           $self;
93             }
94              
95             sub offset
96             {
97             # return the offset of the n'th word into the file
98 0     0     my ($self,$n) = @_;
99              
100 0 0         $n = $self->{_len_s} + $n if $n < 0;
101 0           _offset($self->{_obj},$n);
102             }
103              
104             sub file
105             {
106             # return the dictionary list file
107 0     0     my ($self) = @_;
108              
109 0           $self->{_file};
110             }
111              
112             sub is_valid
113             {
114             # check wether a string conforms to the given charset sets
115 0     0     my $self = shift;
116 0           my $str = shift;
117              
118             # print "$str\n";
119 0 0         return 0 if !defined $str;
120 0 0 0       return 1 if $str eq '' && $self->{_minlen} <= 0;
121              
122 0           my $int = Math::BigInt->bzero();
123 0           my @chars;
124 0 0         if (defined $self->{_sep})
125             {
126 0           @chars = split /$self->{_sep}/,$str;
127 0 0         shift @chars if $chars[0] eq '';
128 0 0         pop @chars if $chars[-1] eq $self->{_sep};
129             }
130             else
131             {
132 0           @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 0 0         return 0 if scalar @chars < $self->{_minlen};
142 0 0         return 0 if scalar @chars > $self->{_maxlen};
143              
144             # further checks for strings longer than 1
145 0           foreach my $c (@chars)
146             {
147 0 0         return 0 if !defined $self->str2num($c);
148             }
149             # all tests passed
150 0           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 0     0     my $self = shift;
158              
159 0 0         return $self->{_len} unless wantarray;
160            
161 0           my @words = ();
162 0           my $OBJ = $self->{_obj};
163 0           for (my $i = 0; $i < $self->{_len}; $i++)
164             {
165 0           push @words, _record($OBJ,$i);
166             }
167 0           @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 0     0     my $self = shift;
175              
176 0           $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 0     0     my $self = shift;
184              
185 0           $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 0     0     my ($c,$x);
192 0 0         if (@_ > 1)
193             {
194             # if two arguments, the first one is the class to "swallow" subclasses
195 0           ($c,$x) = @_;
196             }
197             else
198             {
199 0           $x = shift;
200 0           $c = ref($x);
201             }
202 0 0         return unless ref($x); # only for objects
203              
204 0           my $self = {}; bless $self,$c;
  0            
205 0           foreach my $k (keys %$x)
206             {
207 0 0         if (ref($x->{$k}) eq 'SCALAR')
    0          
    0          
    0          
    0          
208             {
209 0           $self->{$k} = \${$x->{$k}};
  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 0           $self->{$k} = $x->{$k};
216             }
217             elsif (ref($x->{$k}) eq 'ARRAY')
218             {
219 0           $self->{$k} = [ @{$x->{$k}} ];
  0            
220             }
221             elsif (ref($x->{$k}) eq 'HASH')
222             {
223             # only one level deep!
224 0           foreach my $h (keys %{$x->{$k}})
  0            
225             {
226 0           $self->{$k}->{$h} = $x->{$k}->{$h};
227             }
228             }
229             elsif (ref($x->{$k}))
230             {
231 0           my $c = ref($x->{$k});
232 0           $self->{$k} = $c->new($x->{$k}); # no copy() due to deep rec
233             }
234             else
235             {
236             # simple scalar w/o reference
237 0           $self->{$k} = $x->{$k};
238             }
239             }
240 0           $self;
241             }
242              
243             sub chars
244             {
245 0     0     my ($self,$x) = @_;
246              
247             # XXX return always 1 to signal that $x has only one character
248 0           1;
249             }
250              
251             sub count
252             {
253 0     0     my $self = shift;
254              
255 0           $self->{_len};
256             }
257              
258             sub length
259             {
260 0     0     my $self = shift;
261              
262 0           $self->{_len};
263             }
264              
265             sub class
266             {
267 0     0     my $self = shift;
268 0 0         my $class = shift; $class = 0 unless defined $class;
  0            
269              
270             # class(0) is 0
271 0 0         return 0 if $class == 0;
272              
273 0 0         return $self->{_len} if $class == 1;
274              
275 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 0     0     my ($self,$x) = @_;
283              
284 0 0         $x = new Math::BigInt($x) unless ref $x;
285 0 0         return undef if ($x->sign() !~ /^[+-]$/);
286              
287 0           my $l = ''; # $x == 0 as default
288 0           my $int = abs($x->numify());
289 0 0         if ($int > 0)
290             {
291 0           $l = _record($self->{_obj}, $int-1);
292             }
293 0 0         wantarray ? ($l,1) : $l;
294             }
295              
296             sub str2num
297             {
298             # convert Math::String to Math::BigInt
299 0     0     my ($self,$str) = @_;
300              
301 0 0 0       return Math::BigInt->bzero() if !defined $str || $str eq '';
302              
303 0           my $OBJ = $self->{_obj};
304              
305             # do a binary search for the string in the array of strings
306 0           my $left = 0; my $right = $self->{_len_s} - 1;
  0            
307              
308 0           my $leftstr = _record($OBJ,$left);
309 0 0         return Math::BigInt->new($left+1) if $leftstr eq $str;
310 0           my $rightstr = _record($OBJ,$right);
311 0 0         return Math::BigInt->new($right+1) if $rightstr eq $str;
312              
313 0           my $middle;
314 0           while ($right - $left > 1)
315             {
316             # simple middle median computing
317 0           $middle = int(($left + $right) / 2);
318              
319             # advanced middle computing:
320 0           my $ll = ord(substr($leftstr,0,1));
321 0           my $rr = ord(substr($rightstr,0,1));
322 0 0         if ($rr - $ll > 1)
323             {
324 0           my $mm = ord(substr($str,0,1));
325 0 0         $mm++ if $mm == $ll;
326 0 0         $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 0           $middle = $left +
337             int(($mm - $ll) * ($right - $left) / ($rr - $ll));
338 0 0         $middle++ if $middle == $left;
339 0 0         $middle-- if $middle == $right;
340             }
341              
342 0           my $middlestr = _record($OBJ,$middle);
343 0 0         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 0           my $cmp = $middlestr cmp $str;
349             # cmp != 0 here
350 0 0         if ($cmp < 0)
351             {
352 0           $left = $middle; $leftstr = $middlestr;
  0            
353             }
354             else
355             {
356 0           $right = $middle; $rightstr = $middlestr;
  0            
357             }
358             }
359 0 0         return if $right - $left == 1; # not found
360 0           Math::BigInt->new($middle+1);
361             }
362              
363             sub char
364             {
365             # return nth char from charset
366 0     0     my $self = shift;
367 0   0       my $char = shift || 0;
368              
369 0 0         $char = $self->{_len_s} + $char if $char < 0;
370 0           _record($self->{_obj},$char);
371             }
372              
373             sub first
374             {
375 0     0     my $self = shift;
376 0   0       my $count = abs(shift || 0);
377              
378 0 0         return if $count < $self->{_minlen};
379 0 0 0       return if defined $self->{_maxlen} && $count > $self->{_maxlen};
380 0 0         return '' if $count == 0;
381            
382 0           my $str = _record($self->{_obj},0);
383              
384 0 0         return $str if $count == 1;
385            
386 0   0       my $s = $self->{_sep} || '';
387 0           my $res = '';
388 0           for (my $i = 0; $i < $count; $i++)
389             {
390 0           $res .= $s . $str;
391             }
392 0           $s = quotemeta($s);
393 0 0         $res =~ s/^$s// if $s ne ''; # remove first sep
394 0           $res;
395             }
396              
397             sub last
398             {
399 0     0     my $self = shift;
400 0   0       my $count = abs(shift || 0);
401              
402 0 0         return if $count < $self->{_minlen};
403 0 0 0       return if defined $self->{_maxlen} && $count > $self->{_maxlen};
404 0 0         return '' if $count == 0;
405              
406 0           my $str = _record($self->{_obj},$self->{_len_s}-1);
407 0 0         return $str if $count == 1;
408            
409 0           my $res = '';
410 0   0       my $s = $self->{_sep} || '';
411 0           for (my $i = 1; $i <= $count; $i++)
412             {
413 0           $res .= $s . $str;
414             }
415 0           $s = quotemeta($s);
416 0 0         $res =~ s/^$s// if $s ne ''; # remove first sep
417 0           $res;
418             }
419              
420             sub next
421             {
422 0     0     my ($self,$str) = @_;
423              
424 0 0         if ($str->{_cache} eq '') # 0 => 1
425             {
426 0 0         my $min = $self->{_minlen}; $min = 1 if $min <= 0;
  0            
427 0           $str->{_cache} = $self->first($min);
428 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 0           $str->{_cache} = undef;
440             }
441              
442             sub prev
443             {
444 0     0     my ($self,$str) = @_;
445              
446 0 0         if ($str->{_cache} eq '') # 0 => -1
447             {
448 0 0         my $min = $self->{_minlen}; $min = -1 if $min >= 0;
  0            
449 0           $str->{_cache} = $self->first($min);
450 0           return;
451             }
452              
453             # extract the current value
454             #$str->{_cache} = _record($self->{_obj}, $str->numify()-1);
455 0           $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__