File Coverage

blib/lib/Chorus/Frame.pm
Criterion Covered Total %
statement 261 297 87.8
branch 102 162 62.9
condition 16 36 44.4
subroutine 54 56 96.4
pod 6 30 20.0
total 439 581 75.5


line stmt bran cond sub pod time code
1             package Chorus::Frame;
2              
3 7     7   137747 use 5.006;
  7         27  
4 7     7   39 use strict;
  7         15  
  7         412  
5              
6             our $VERSION = '1.03';
7              
8             =head1 NAME
9              
10             Chorus::Frame - A short implementation of frames from knowledge representation.
11              
12             =head1 VERSION
13              
14             Version 1.04
15              
16             =cut
17              
18             =head1 SYNOPSIS
19              
20             use Chorus::Frame;
21            
22             my $f1 = Chorus::Frame->new(
23             b => {
24             _DEFAULT => 'inherited default for b'
25             }
26             );
27              
28             my $f2 = Chorus::Frame->new(
29             a => {
30             b1 => sub { $SELF->get('a b2') }, # procedural attachment using context $SELF
31             b2 => {
32             _ISA => $f1->{b},
33             _NEEDED => 'needed for b # needs mode Z to precede inherited _DEFAULT
34             }
35             }
36             );
37            
38             Chorus::Frame::setMode(GET => 'N');
39             print $f2->get('a b1') . "\n"; # print 'inherited default for b'
40              
41             Chorus::Frame::setMode(GET => 'Z');
42             print $f2->get('a b1') . "\n"; # print 'needed for b'
43            
44             =cut
45              
46             =head1 DESCRIPTION
47              
48             - A frame is a generic object structure described by slots (properties).
49             - A frame can inherit slots from other frames.
50             - A frame can have specific slots describing :
51            
52             * how it can be associated to a target information,
53             * how he reacts when its target information changes
54             * what it can try when a missing property is requested.
55            
56             - The slots _VALUE,_DEFAULT,_NEEDED are tested in this order to obtain the target information
57             of a given frame (can be inherited).
58             - Two other special slots _BEFORE & _AFTER can define what a frame has to do before or after
59             one of its properties changes.
60             - The slot _ISA is used to define the inheritance.
61              
62             Two modes 'N' (default) or 'Z' are used to define the priority between a frame and its inherited
63             frames in order to process its target information
64            
65             The globale variable $SELF returns the current CONTEXT which is the most recent frame called for the method get().
66             A slot defined by a function sub { .. } can refer to the current context $SELF in its body.
67            
68             All frames are automaticaly referenced in a repository used to optimise the selection of frames for a given action.
69             The function fmatch() can be used to quicly select all the frames responding to a given test on their properties.
70             =cut
71              
72             BEGIN {
73 7     7   38 use Exporter;
  7         15  
  7         368  
74 7     7   38 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  7         13  
  7         814  
75              
76 7     7   71 @ISA = qw(Exporter);
77 7         20 @EXPORT = qw($SELF &fmatch REQUIRE_FAILED);
78 7         147 @EXPORT_OK = qw(%FMAP);
79              
80             # %EXPORT_TAGS = ( ); # eg: TAG => [ qw!name1 name2! ];
81             }
82              
83 7     7   49 use strict;
  7         29  
  7         169  
84 7     7   35 use Carp; # warn of errors (from perspective of caller)
  7         13  
  7         498  
85 7     7   37 use Digest::MD5;
  7         14  
  7         308  
86 7     7   38 use Scalar::Util qw(weaken);
  7         19  
  7         607  
87              
88 7     7   7399 use Data::Dumper;
  7         77313  
  7         505  
89              
90 7     7   53 use constant DEBUG_MEMORY => 0;
  7         13  
  7         427  
91              
92 7     7   38 use vars qw($AUTOLOAD);
  7         14  
  7         298  
93              
94 7     7   41 use constant SUCCESS => 1;
  7         14  
  7         333  
95 7     7   39 use constant FAILED => 0;
  7         14  
  7         332  
96 7     7   36 use constant REQUIRE_FAILED => -1;
  7         12  
  7         403  
97              
98 7     7   36 use constant VALUATION_ORDER => ('_VALUE', '_DEFAULT', '_NEEDED');
  7         14  
  7         405  
99              
100 7     7   37 use constant MODE_N => 1;
  7         13  
  7         377  
101 7     7   37 use constant MODE_Z => 2;
  7         12  
  7         29289  
102              
103             my $getMode = MODE_N; # DEFAULT IS N !!!
104              
105             my %REPOSITORY;
106             my %FMAP;
107             my %INSTANCES;
108              
109             our $SELF;
110             my @Heap = ();
111              
112             sub AUTOLOAD {
113 15   33 15   130 my $frame = shift || $SELF;
114 15         30 my $slotName = $AUTOLOAD;
115 15         60 $slotName =~ s/.*://; # strip fully-qualified portion
116 15         45 get($frame, $slotName, @_); # or getN or getZ !!
117             }
118              
119             sub _isa {
120 482     482   757 my ($ref, $str) = @_;
121 482         1773 return (ref($ref) eq $str);
122             }
123              
124             =head1 SUBROUTINES
125             =cut
126              
127             =head2 setMode
128              
129             Defines the inheritance mode of methods get() for the special slots _VALUE,_DEFAULT,_NEEDED
130             the default mode is 'N'.
131            
132             'N' : ex. a single slot from the sequence _VALUE,_DEFAULT,_NEEDED will be tested in all inherited
133             frames before trying the next one.
134            
135             'Z' : the whole sequence _VALUE,_DEFAULT,_NEEDED will be tested from the frame before being
136             tested from the inherited frames
137            
138             ex. Chorus::Frame::setMode(GET => 'Z');
139              
140             =cut
141              
142             sub setMode {
143 2     2 1 10 my (%opt) = @_;
144 2 100 66     17 $getMode = MODE_N if defined($opt{GET}) and uc($opt{GET}) eq 'N';
145 2 100 66     16 $getMode = MODE_Z if defined($opt{GET}) and uc($opt{GET}) eq 'Z';
146             }
147              
148             =head1 METHODS
149             =cut
150              
151             =head2 _keys
152              
153             my @k = $f->_keys;
154             same as CORE::keys but excludes the special slot '_KEY' specific to all frames
155             =cut
156              
157             sub _keys {
158 0     0   0 my ($this) = @_;
159 0         0 grep { $_ ne '_KEY' } keys %{$this};
  0         0  
  0         0  
160             }
161              
162             sub pushself {
163 26 100   26 0 75 unshift(@Heap, $SELF) if $SELF;
164 26         46 $SELF = shift;
165             }
166              
167             sub popself {
168 26     26 0 50 $SELF = shift @Heap;
169             }
170              
171             sub expand {
172 79     79 0 150 my ($info, @args) = @_;
173 79 100       158 return expand(&$info(@args)) if _isa($info, 'CODE');
174 75         231 return $info;
175             }
176              
177             =head2 _push
178              
179             push new elements to a given slot (becomes an array if necessary)
180             =cut
181              
182             sub _push {
183 1     1   5 my ($this, $slot, @elems) = @_;
184 1 50       4 return unless scalar(@elems);
185 1 50 33     8 $this->{$slot} = [ $this->{$slot} || () ] unless ref($this->{$slot}) eq 'ARRAY';
186 1         2 unshift @{$this->{$slot}}, @elems;
  1         5  
187             }
188              
189             sub _addInstance {
190 14     14   37 my ($this, $instance) = @_;
191 14         37 my $k = $instance->{_KEY};
192            
193 14         45 $INSTANCES{$this->{_KEY}}->{$k} = $instance;
194 14         109 weaken($INSTANCES{$this->{_KEY}}->{$k}); # will not increase garbage ref counter to $instance !!
195             }
196              
197             =head2 _inherits
198              
199             add inherited new frame(s) outside constructor
200             ex. $f->_inherits($F1,$F2);
201             =cut
202            
203             sub _inherits {
204 1     1   5 my ($this, @inherited) = @_;
205 1         3 my $k = $this->{_KEY};
206 1         3 for (grep { ! $INSTANCES{$_->{_KEY}}->{$k} } @inherited) { # clean list
  1         4  
207 1         4 $_->_addInstance($this);
208 1         3 $this->_push('_ISA', $_);
209             }
210 1         3 return $this;
211             }
212              
213             sub _removeInstance {
214 0     0   0 my ($this, $instance) = @_;
215 0         0 my $k = $instance->{_KEY};
216 0 0       0 (warn "Instance NOT FOUND !?", return) unless $INSTANCES{$this->{_KEY}}->{$k};
217 0         0 delete $INSTANCES{$this->{_KEY}}->{$k};
218             }
219              
220             sub blessToFrame {
221              
222             sub register {
223              
224 39     39 0 64 my ($this) = @_;
225              
226 39         58 my $k;
227             do {
228 39         697 $k = Digest::MD5::md5_base64( rand );
229 39         55 } while(exists($FMAP{$k}));
230            
231 39         158 foreach my $slot (keys(%$this)) { # register all slots
232 68 100       208 $REPOSITORY{$slot} = {} unless exists $REPOSITORY{$slot};
233 68         215 $REPOSITORY{$slot}->{$k} = 'Y';
234             }
235            
236 39         87 $this->{_KEY} = $k;
237 39         80 $FMAP{$k} = $this;
238 39         108 weaken($FMAP{$k}); # will not increase garbage ref counter to $this !!
239 39         86 return $this;
240             }
241              
242             sub blessToFrameRec {
243              
244 39     39 0 67 local $_ = shift;
245              
246 39 50       82 if (_isa($_,'Chorus::Frame')) {
247            
248 39         153 while(my ($k, $val) = each %$_) {
249 107 100       202 if (_isa($val,'HASH')) {
250 15 50       47 next if $val->{_NOFRAME};
251 15         38 bless($val, 'Chorus::Frame');
252 15         38 $val->register();
253 15         61 blessToFrameRec($val);
254             } else {
255 92 50       176 if (_isa($val,'ARRAY')) {
256 0         0 blessToFrameRec($_->{$k});
257             }
258             }
259 107 100       500 if ($k eq '_ISA') {
260 13 50       29 foreach my $inherited (_isa($val,'ARRAY') ? map \&expand, @{$val}
  0         0  
261             : (expand($val))) {
262 13 50       556 $inherited->_addInstance($_) if $inherited;
263             }
264             }
265             }
266              
267 39         82 return;
268             }
269              
270 0 0       0 if (_isa($_,'ARRAY')) { # à revoir (sans $idx)
271 0         0 foreach my $idx (0 .. scalar(@$_) - 1) {
272 0 0       0 if (_isa($_[$idx], 'HASH')) {
273 0 0       0 next if exists $_[$idx]->{_NOFRAME};
274 0         0 bless($_[$idx], 'Chorus::Frame');
275 0         0 $_[$idx]->register();
276 0         0 blessToFrameRec($_[$idx]);
277             } else {
278 0 0       0 if (_isa($_[$idx],'ARRAY')) {
279 0         0 blessToFrameRec($_[$idx]);
280             }
281             }
282             }
283             }
284             }
285              
286 28     28 0 52 my $res = shift;
287              
288 28 50       70 return $res if _isa($res, 'Chorus::Frame'); # already blessed
289              
290             SWITCH: {
291              
292 28 100       52 _isa($res, 'HASH') && do {
  28         75  
293 24 50       87 return $res if exists $res->{_NOFRAME};
294 24         79 bless($res, 'Chorus::Frame')->register();
295 24 50       94 blessToFrameRec $res if keys(%$res);
296 24         61 last SWITCH;;
297             };
298              
299 4 50       14 _isa($res, 'ARRAY') && do {
300 0 0       0 return $res unless scalar(@$res);
301 0         0 blessToFrameRec $res;
302 0         0 last SWITCH;
303             };
304              
305             }; # SWITCH
306              
307 28         119 return $res;
308             }
309              
310             =head2 new
311              
312             Constructor : Converts a hashtable definition into a Chorus::Frame object.
313            
314             Important - All nested hashtables are recursively converted to Chorus::Frame,
315             except those providing a slot _NO_FRAME
316            
317             All frames are associated to a unique key and registered in an internal repository (see fmatch)
318            
319             Ex. $f = Chorus::Frame->new(
320             slotA1 => {
321             _ISA => [ $f2->slotA, $f3->slotA ] # multiple inheritance
322             slotA2 => sub { $SELF }; # procedural attachements
323             slotA3 => 'value for A3'
324             },
325             slotB => {
326             _NEEDED => sub { .. }
327             }
328             );
329             =cut
330              
331             sub new {
332 24     24 1 2583 my ($this, @desc) = @_;
333 24         104 return blessToFrame({@desc});
334             }
335              
336             # WARN - Should be automatically called by carbage collector EVEN with those 2 remaining references : $INSTANCES{$k} AND $FMAP{$k} !!!
337             #
338             sub DESTROY {
339 37     37   812 my ($this) = @_;
340              
341 37 50       130 my $k = $this->{_KEY} or warn "Undefined _KEY(1) for " . Dumper($this);
342              
343 37 100       117 delete $INSTANCES{$k} if exists $INSTANCES{$k};
344              
345 37 100       112 foreach my $inherited (_isa($this->{_ISA}, 'ARRAY') ? map \&expand, @{$this->{_ISA}} : (expand($this->{_ISA}))) {
  1         5  
346 37 100       152 my $ik = $inherited->{_KEY} or next;
347 12 50       65 delete $INSTANCES{$ik}->{$k} if exists $INSTANCES{$ik}->{$k};
348             }
349              
350 36         128 foreach my $slot (keys(%$this)) {
351 92 50 66     423 delete($REPOSITORY{$slot}->{$k}) if exists $REPOSITORY{$slot} and exists $REPOSITORY{$slot}->{$k};
352             }
353              
354 36         310 delete $FMAP{$k}; # is a weak reference (not counted by garbage collector)
355             }
356              
357             =head2 get
358              
359             This method provides the information associated to a sequence of slots.
360             This sequence is given in a string composed with slot names separated by spaces.
361             The last slot is tested for the target information with the sequence _VALUE,_DEFAULT,_NEEDED.
362             If a frame doesn't provide any of those slots, the target information is the frame itself.
363              
364             A frame called with the method get() becomes the current context wich can be referred with the variable $SELF.
365            
366             Note - The short form $f->SLOTNAME() can by used instead of $f->get('SLOTNAME');
367            
368             Ex. $f->foo; # equiv to $f->get('foo');
369             $f->foo(@args); # equiv to $f->get('foo')(@args);
370              
371             $f->get('foo bar'); # $SELF (context) is $f while processing 'bar'
372              
373             $f->get('foo')->get('bar') # $SELF (context) is $f->foo while processing 'bar'
374             $f->foo->bar; # short form
375            
376             =cut
377              
378             sub get {
379            
380             sub expandInherits {
381              
382             sub first { # uses expand
383 12     12 0 22 my ($this, $slots, @args) = @_;
384 12         14 for (@{$slots}) {
  12         26  
385 14 100       51 return { ret => SUCCESS, res => expand($this->{$_}, @args) } if exists $this->{$_};
386             }
387 7         15 return undef;
388             }
389              
390 12     12 0 20 my ($this,$tryValuations,@args) = @_;
391              
392 12         28 my $res = $this->first($tryValuations,@args);
393 12 50 66     55 return $res if defined($res) and $res->{ret};
394              
395 7 100       26 if (exists($this->{_ISA})) {
396 2 0 33     5 my @h = _isa($this->{_ISA}, 'ARRAY') ? map { expand($_) || () } @{$this->{_ISA}} : (expand($this->{_ISA}) || ());
  0 50       0  
  0         0  
397 2         5 for (@h) { # upper level
398 2         7 $res = $_->expandInherits($tryValuations,@args);
399 2 100 33     17 return $res if defined($res) and $res->{ret};
400             }
401             }
402 6         21 return { ret => FAILED };
403             } # expandInherits
404              
405             sub inherited {
406 41     41 0 90 my ($this,$slot,@rest) = @_;
407              
408 41 100       179 return $this->{$slot} if exists($this->{$slot}); # first that match (better than buildtree) !!
409 12 100       43 $this->{_ISA} and push @rest, _isa($this->{_ISA}, 'ARRAY') ? @{$this->{_ISA}} : $this->{_ISA}; # see expand
  1 100       3  
410              
411 12         27 my $next = shift @rest;
412 12 100       37 return undef unless $next;
413 10         34 return $next->inherited($slot,@rest);
414             }
415              
416             sub getZ {
417            
418             sub value_Z {
419 2     2 0 5 my ($info, @args) = @_;
420 2 100       5 return expand($info,@args) unless _isa($info,'Chorus::Frame');
421 1         5 my $res = $info->expandInherits([VALUATION_ORDER], @args);
422 1 50 33     11 return $res->{res} if defined($res) and $res->{ret};
423 0         0 return $info;
424             }
425            
426 4     4 0 9 my ($this, $way, @args) = @_;
427              
428 4 50       11 return $this->value_Z(@args) unless $way;
429              
430 4 50       21 $way =~ /^\s*(\S*)\s*(.*?)\s*$/o or die "Unexpected way format : '$way'";
431 4         13 my ($nextStep, $followWay) = ($1,$2);
432              
433 4 100       11 return value_Z($this->inherited($nextStep), @args) unless $followWay;
434              
435 2         7 my $next = $this->inherited($nextStep);
436 2 50       5 return _isa($next,'Chorus::Frame') ? $next->getZ($followWay, @args) : undef;
437             }
438              
439             sub getN {
440            
441             sub value_N {
442 22     22 0 42 my ($info, @args) = @_;
443 22 100       55 return expand($info,@args) unless _isa($info,'Chorus::Frame');
444 4         15 for (VALUATION_ORDER) {
445 9         35 my $res = $info->expandInherits([$_], @args);
446 9 100 33     64 return $res->{res} if defined($res) and $res->{ret};
447             }
448 0         0 return $info;
449             }
450            
451 26     26 0 66 my ($this, $way, @args) = @_;
452              
453 26 50       76 return $this->value_N(@args) unless $way;
454              
455 26 50       139 $way =~ /^\s*(\S*)\s*(.*?)\s*$/o or die "Unexpected way format : '$way'";
456 26         78 my ($nextStep, $followWay) = ($1,$2);
457              
458 26 100       108 return value_N($this->inherited($nextStep), @args) unless $followWay;
459              
460 4         14 my $next = $this->inherited($nextStep);
461 4 50       10 return _isa($next,'Chorus::Frame') ? $next->getN($followWay, @args) : undef;
462             }
463              
464 21     21 1 68 pushself(shift);
465 21 100       84 my $res = $getMode == MODE_N ? getN($SELF,@_) : getZ($SELF,@_);
466 21         54 popself();
467 21         100 return $res;
468             }
469              
470             =head2 delete
471              
472             All Frames properties are registered in a single table, especially to optimize the method fmatch().
473             This why frames have to use the form $f->delete($slotname) instead of delete($f->{$slotname})
474             otherwise a frame will be considered by fmatch() as providing a slot even after this one have been removed.
475              
476             =cut
477            
478             sub delete {
479            
480             sub deleteSlot {
481              
482             sub unregisterSlot {
483 1     1 0 3 my ($this,$slot) = @_;
484 1 50       4 return unless exists $REPOSITORY{$slot};
485 1 50       8 delete $REPOSITORY{$slot}->{$this->{_KEY}} if exists $REPOSITORY{$slot}->{$this->{_KEY}};
486             }
487              
488 1     1 0 2 my ($this,$slot) = @_;
489              
490 1         4 $this->unregisterSlot($slot);
491 1 50       10 delete($this->{$slot}) if exists $this->{$slot};
492             }
493            
494             sub deleteN {
495              
496 2     2 0 4 my ($this, $way) = @_;
497              
498 2 50       6 return undef unless $way;
499              
500 2 50       13 $way =~ /^\s*(\S*)\s*(.*?)\s*$/o or die "Unexpected way format : '$way'";
501 2         8 my ($nextStep, $followWay) = ($1,$2);
502              
503 2 100       10 return $this->deleteSlot($nextStep) unless $followWay;
504              
505 1         3 my $next = $this->inherited($nextStep);
506 1 50       4 return _isa($next,'Chorus::Frame') ? $next->deleteN($followWay) : undef;
507             }
508            
509 1     1 1 3 pushself(shift);
510 1         5 my $res = $SELF->deleteN(@_);
511 1         3 popself();
512 1         2 return $res;
513             }
514              
515             =head2 set
516              
517             This method tells a frame to associated target information to a sequence of slots
518             A frame called for this method becomes the new context.
519              
520             Ex. $f1 = Chorus::Frame->new(
521             a => {
522             b => {
523             c => 'C'
524             }
525             }
526             );
527            
528             $f1->set('a b', 'B'); # 'B' becomes the target _VALUE for $f1->get('a b')
529             $f1->get('a b'); # returns 'B'
530              
531             $f1->get('a b c'); # still returns 'C'
532             $f1->delete('a b c');
533             $f1->get('a b c'); # undef
534              
535             $f2 = Chorus::Frame->new(
536             _ISA => $1,
537             );
538              
539             $f2->get('a b c'); # returns 'C'
540            
541             $f2->set('a b', 'AB'); # cancel inheritance for first slot 'a'
542             $f2->get('a b'); # returns 'AB'
543              
544             $f2->get('a b c'); # undefined
545            
546             =cut
547              
548             sub set {
549              
550             sub registerSlot {
551 4     4 0 8 my ($this,$slot) = @_;
552 4 100       18 $REPOSITORY{$slot} = {} unless exists $REPOSITORY{$slot};
553 4         13 $REPOSITORY{$slot}->{$this->{_KEY}} = 'Y';
554             }
555            
556             sub setValue {
557 1     1 0 2 my ($this, $val) = @_;
558              
559 1 50       5 return undef if $this->getN('_REQUIRE', $val) == REQUIRE_FAILED;
560            
561 1         4 $this->getN('_BEFORE', $val); # or return undef;
562              
563 1         3 blessToFrame($val);
564 1         2 $this->{'_VALUE'} = $val;
565 1         4 $this->registerSlot('_VALUE');
566              
567 1         4 $this->getN('_AFTER', $val); # or return undef;
568              
569 1         6 return $val;
570             }
571              
572             sub setSlot {
573 3     3 0 8 my ($this, $slot, $info) = @_;
574 3         6 blessToFrame($info);
575 3         8 $this->{$slot} = $info;
576 3         11 $this->registerSlot($slot);
577 3         22 return $info;
578             }
579            
580             sub setN {
581 6     6 0 14 my ($this, $way, $info) = @_;
582              
583 6 100       30 return $this->setValue($info) unless $way;
584              
585 5 50       32 $way =~ /^\s*(\S*)\s*(.*?)\s*$/o or die "Unexpected way format : '$way'";
586 5         18 my ($nextStep, $followWay) = ($1,$2);
587 5         11 my $crossedValue = $this->{$nextStep};
588              
589 5 100       16 return $crossedValue->setN($followWay, $info) if _isa($crossedValue,'Chorus::Frame');
590            
591 3 50       16 unless ($followWay) {
592 3 50       9 if ($nextStep eq '_VALUE') {
593 0         0 return $this->setValue($info);
594             } else {
595 3 50 33     17 if (_isa($this->{$nextStep}, 'Chorus::Frame') and exists($this->{$nextStep}->{_VALUE})) {
596 0         0 return $this->{$nextStep}->setValue($info)
597             } else {
598 3         15 return $this->setSlot($nextStep, $info);
599             }
600             }
601             }
602              
603 0 0       0 $this->{$nextStep} = (exists($this->{$nextStep})) ? new Chorus::Frame (_VALUE => $crossedValue)
604             : new Chorus::Frame;
605            
606 0         0 return $this->{$nextStep}->setN($followWay, $info); # (keep current context)
607            
608             } # set is setN !!!!
609            
610 4     4 1 30 pushself(shift);
611 4         18 my %desc = @_;
612              
613 4         6 my $res;
614            
615 4         17 while(my($k,$val) = each %desc) {
616 4         15 $res = $SELF->setN($k, $val); # set is setN !!
617             }
618            
619 4         11 popself();
620 4         14 return $res; # wil return last set if multiple pairs (key=>val) !!
621             }
622              
623             =head2 fmatch
624              
625             This function returns the list of the frames providing all the slots given as argument.
626             The result can contains the frames providing these the slots by inheritance.
627             This function can be used to minimise the list of frames that should be candidate for a given process.
628            
629             An optional argument 'from' can provide a list of frames as search space
630            
631             ex. @l = grep { $_->score > 5 } fmatch(
632             slot => ['foo', 'score'],
633             from => \@framelist # optional : limit search scope
634             );
635             #
636             # all frames, optionnaly from @framelist, providing both slots 'foo' and 'score' (possible
637             # inheritance) and on which the method get('score') returns a value > 5
638              
639             =cut
640              
641             sub firstInheriting {
642 10     10 0 13 my ($this) = @_;
643 10         21 my $k = $this->{_KEY};
644 10 50       70 return () unless $INSTANCES{$k};
645 0         0 return(values(%{$INSTANCES{$k}}));
  0         0  
646            
647             } # firstInheriting
648              
649             sub fmatch {
650            
651             sub framesProvidingSlot { # inheritance ok
652            
653             sub hasSlot {
654 5     5 0 7 my ($slot) = @_;
655 5 50       11 return map { $FMAP{$_} || () } keys(%{$REPOSITORY{$slot}})
  10         36  
  5         17  
656             }
657              
658             sub wholeTree {
659            
660 5     5 0 10 my ($res, @dig) = @_;
661 5 50       25 return $res unless $dig[0];
662 0 0       0 my @inheriting = map { $_->firstInheriting || () } @dig;
  0         0  
663 0         0 push(@$res, @inheriting);
664 0         0 return wholeTree($res,@inheriting);
665            
666             } # wholeTree
667            
668 5     5 0 11 my ($slot) = @_;
669            
670 5         15 my @res = hasSlot($slot);
671 5 50       12 my @inheriting = map { $_->firstInheriting || () } @res;
  10         25  
672            
673 5         9 push @res, @inheriting;
674 5         15 return wholeTree(\@res, @inheriting);
675            
676             } # framesProvidingSlot
677              
678 4     4 1 1370 my %opts = @_;
679 4 100 33     14 $opts{slot} = [ $opts{slot} || () ] unless _isa($opts{slot},'ARRAY');
680 4 50       9 my ($firstslot,@otherslots) = @{$opts{slot} || []};
  4         31  
681              
682 4 50       13 return () unless $firstslot;
683            
684 4         7 my %filter = map { $_->{_KEY} => 'Y' || () } @{framesProvidingSlot($firstslot)};
  9         25  
  4         12  
685            
686 4         17 for(@otherslots) {
687 1 50       3 %filter = map { $filter{$_->{_KEY}} ? ($_->{_KEY} => 'Y') : () } @{framesProvidingSlot($_)};
  1         10  
  1         3  
688             }
689            
690 4 100       13 if ($opts{from}) {
691 1         2 return grep { $filter{$_->{_KEY}} } @{$opts{from}};
  2         9  
  1         3  
692             }
693            
694 3 50       12 return map { $FMAP{$_} || ()} keys(%filter);
  4         19  
695            
696             } # fmatch
697              
698             =head1 AUTHOR
699              
700             Christophe Ivorra, C<< >>
701              
702             =head1 BUGS
703              
704             Please report any bugs or feature requests to C, or through
705             the web interface at L. I will be notified, and then you'll
706             automatically be notified of progress on your bug as I make changes.
707              
708             =head1 SUPPORT
709              
710             You can find documentation for this module with the perldoc command.
711              
712             perldoc Chorus::Frame
713              
714              
715             You can also look for information at:
716              
717             =over 4
718              
719             =item * RT: CPAN's request tracker (report bugs here)
720              
721             L
722              
723             =item * AnnoCPAN: Annotated CPAN documentation
724              
725             L
726              
727             =item * CPAN Ratings
728              
729             L
730              
731             =item * Search CPAN
732              
733             L
734              
735             =back
736              
737              
738             =head1 ACKNOWLEDGEMENTS
739              
740              
741             =head1 LICENSE AND COPYRIGHT
742              
743             Copyright 2013 Christophe Ivorra.
744              
745             This program is free software; you can redistribute it and/or modify it
746             under the terms of either: the GNU General Public License as published
747             by the Free Software Foundation; or the Artistic License.
748              
749             See http://dev.perl.org/licenses/ for more information.
750              
751              
752             =cut
753              
754             1; # End of Chorus::Frame