1: #!/usr/bin/perl
  2: # Copyright (c) 1998 Robert Braddock. All rights reserved.
  3: # This program is free software; you can redistribute it and/or
  4: # modify it under the same terms as Perl itself.

  6: package Hash::Layered;
  7: use strict;

  9: use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION $REVISION);  BEGIN {
 10:     @ISA =       qw( Exporter Tie::Hash );
 11:     @EXPORT =      ();
 12:     @EXPORT_OK =   ();
 13:     %EXPORT_TAGS = ();
 14:     $VERSION =     0.3;
 15:     $REVISION =   q$Revision: 1.2 $;
 16: }
 17: use Tie::Hash;
 18: use Exporter;


 21: use Carp;

 23: my $DEBUG = 0;

 25: #Short hand for object data structure. You need to be able to expand these
 26: #abbreviations to read the module source
 27: my ($dls,    #default layer state
 28:     $ldc,    #layer data count  (aka index of "current layer")
 29:     $ldl,    #layer data list
 30:              #   list( list(layer_sub_id, transparency, var_name...), ... )
 31: #    $dvs,    #default variable  state
 32:     $vvh,    #variable value-stack hash
 33:              #hash{ var_name -> list(var1.layer_id,var1.value, ...) }
 34: #    $vdc,    #variable data count
 35: #    $vdl,    #variable data list
 36: #             #   list( list(var_id,transparency) )
 37: #    $ilc,    #_intervening_layer cache
 38:     $itr,    #iterator data
 39:     $obj_size) = (0..20);

 41: #Structure for Layer Data List elements
 42: my ($ldl_subid,  #index-unique part for id
 43:     $ldl_sem,  #semantics state
 44:     $ldl_vars,   #first variable name defined in this layer
 45:                  #... rest of variable names defined at this layer
 46:     ) = (0..20);

 48: #Structure for Intervening Layer Cache
 49: #my ($ilc_valid,  #is the cache valid
 50: #    $ilc_offset, #offset for looking up layers
 51: #    ) = (0..20);

 53: #Structure for Variable Data List
 54: #my ($vdl_id,     #ID for variable
 55: #    $vdl_sem,  #semantics state
 56: #    ) = (0..20);


 59: #Used to set the semantics for the layering
 60: my %Semantics = ('opaque'=>'opaque',           'obstruct'=>'opaque',
 61:                  'transparent'=>'transparent', 'flat'=>'transparent',
 62:                  'oneway'=>'oneway',           'protect'=>'oneway',
 63:                  'invisible'=>'invisible',     'hidden'=>'invisible',
 64:                  'biased'=>'biased',           'cascade'=>'biased',
 65:                  'default'=>'default');

 67: sub whowasi { (caller(1))[3] . '()' }
 68: sub trace { $DEBUG = @_ ? shift : 1 }

 70: #note doesn't carry default state from created object
 71: sub new {
 72:     my $proto = shift;
 73:     my $class = ref($proto) || $proto;
 74:     my $self;
 75:     my $realself = {};
 76:     tie %$realself, $class.'::_Internal';
 77:     $self = bless $realself, $class;
 78:     $self->push;
 79:     #warn "new: SELF:$self    REAL:$realself   HASH:$hash\n";
 80:     return $self;
 81: }
 82: sub new_alt {
 83:     my $proto = shift;
 84:     my $class = ref($proto) || $proto;
 85:     my $self;
 86:     my %hash;# = {}; #this seems critical to calling tied in methods
 87:     #my $realself = 
 88:     tie %hash, $class.'::_Internal';
 89:     #$self = $hash;
 90:     $self = bless \%hash, $class;
 91:     $self->push;
 92:     #warn "new: SELF:$self    REAL:$realself   HASH:$hash\n";
 93:     return $self;
 94: }


 97: sub set_default {
 98:     carp &whowasi if $DEBUG;
 99:     my ($self,$state) = @_;
100:     $self = tied %$self if ref($self) eq __PACKAGE__;
101:     $state = lc $state;
102:     #if ($^W) {
103:     carp "'$state' not a valid default layer state"
104:         unless (defined $state && defined $Semantics{$state} &&
105:                 $Semantics{$state} ne 'default');
106:     #}
107:     #clear cache
108:     #$self->[$ilc]->[$ilc_valid] = 0;
109:     #replace state
110:     ($state, $self->[$dls]) = ($self->[$dls], $Semantics{$state});
111:     warn "SEMANTICS:default $state => ".$self->[$dls]."\n" if $DEBUG;
112:     return $state;
113: }

115: sub set_layer {
116:     carp &whowasi if $DEBUG;
117:     my ($self,$state,$layerid) = @_;
118:     $self = tied %$self if ref($self) eq __PACKAGE__;
119:     $state = lc $state;
120:     #if ($^W) {
121:     carp "'$state' not a valid layer state"
122:         unless (defined $state && defined $Semantics{$state});
123:     #}
124:     my $l_data;
125:     if (defined $layerid && $layerid ne '') {
126:         $layerid =~ /^(\d+)/;
127:         $l_data = $1;
128:         $l_data = $self->[$ldl]->[$l_data] if defined $l_data;
129:         croak "'$layerid' does not refer to a valid layer"
130:             unless defined $l_data;
131:     } else {
132:         $layerid = $self->_id($self->[$ldc]);
133:         $l_data = $self->[$ldl]->[$self->[$ldc]];
134:     }
135:     #clear cache
136:     #$self->[$ilc]->[$ilc_valid] = 0;
137:     #replace state
138:     ($state, $l_data->[$ldl_sem]) = ($l_data->[$ldl_sem], $Semantics{$state});
139:     warn "SEMANTICS:$layerid $state => ".$l_data->[$ldl_sem]."\n" if $DEBUG;

141:     return $state;
142: }

144: sub set_key {
145:     croak "set_key not implemented";
146: }

148: sub set_key_default {
149:     croak "set_key_default not implemented";
150: }

152: #? with arg means set for that variable key
153: sub default_opaque {
154:     carp &whowasi if $DEBUG;
155:     my $self = shift;
156:     return $self->set_default('opaque') if (@_ == 0);
157:     return $self->set_key_default('opaque',@_);
158: }
159: sub default_obstruct {
160:     carp &whowasi if $DEBUG;
161:     my $self = shift;
162:     return $self->set_default('opaque') if (@_ == 0);
163:     return $self->set_key_default('opaque',@_);
164: }
165: sub default_transparent {
166:     carp &whowasi if $DEBUG;
167:     my $self = shift;
168:     return $self->set_default('transparent') if (@_ == 0);
169:     return $self->set_key_default('transparent',@_);
170: }
171: sub default_flat {
172:     carp &whowasi if $DEBUG;
173:     my $self = shift;
174:     return $self->set_default('transparent') if (@_ == 0);
175:     return $self->set_key_default('transparent',@_);
176: }
177: sub default_oneway {
178:     carp &whowasi if $DEBUG;
179:     my $self = shift;
180:     return $self->set_default('oneway') if (@_ == 0);
181:     return $self->set_key_default('oneway',@_);
182: }
183: sub default_protect {
184:     carp &whowasi if $DEBUG;
185:     my $self = shift;
186:     return $self->set_default('oneway') if (@_ == 0);
187:     return $self->set_key_default('oneway',@_);
188: }
189: sub default_biased {
190:     carp &whowasi if $DEBUG;
191:     my $self = shift;
192:     return $self->set_default('biased') if (@_ == 0);
193:     return $self->set_key_default('biased',@_);
194: }
195: sub default_cascade {
196:     carp &whowasi if $DEBUG;
197:     my $self = shift;
198:     return $self->set_default('biased') if (@_ == 0);
199:     return $self->set_key_default('biased',@_);
200: }
201: sub default_invisible {
202:     carp &whowasi if $DEBUG;
203:     my $self = shift;
204:     return $self->set_default('invisible') if (@_ == 0);
205:     return $self->set_key_default('invisible',@_);
206: }
207: sub default_hidden {
208:     carp &whowasi if $DEBUG;
209:     my $self = shift;
210:     return $self->set_default('invisible') if (@_ == 0);
211:     return $self->set_key_default('invisible',@_);
212: }
213: sub opaque {
214:     carp &whowasi if $DEBUG;
215:     my $self = shift;
216:     return $self->set_layer('opaque') if (@_ == 0);
217:     return $self->set_key('opaque',@_);
218: }
219: sub obstruct {
220:     carp &whowasi if $DEBUG;
221:     my $self = shift;
222:     return $self->set_layer('opaque') if (@_ == 0);
223:     return $self->set_key('opaque',@_);
224: }
225: sub oneway {
226:     carp &whowasi if $DEBUG;
227:     my $self = shift;
228:     return $self->set_layer('oneway') if (@_ == 0);
229:     return $self->set_key('oneway',@_);
230: }
231: sub protect {
232:     carp &whowasi if $DEBUG;
233:     my $self = shift;
234:     return $self->set_layer('oneway') if (@_ == 0);
235:     return $self->set_key('oneway',@_);
236: }
237: sub biased {
238:     carp &whowasi if $DEBUG;
239:     my $self = shift;
240:     return $self->set_layer('biased') if (@_ == 0);
241:     return $self->set_key('biased',@_);
242: }
243: sub cascade {
244:     carp &whowasi if $DEBUG;
245:     my $self = shift;
246:     return $self->set_layer('biased') if (@_ == 0);
247:     return $self->set_key('biased',@_);
248: }
249: sub transparent {
250:     carp &whowasi if $DEBUG;
251:     my $self = shift;
252:     return $self->set_layer('transparent') if (@_ == 0);
253:     return $self->set_key('transparent',@_);
254: }
255: sub flat {
256:     carp &whowasi if $DEBUG;
257:     my $self = shift;
258:     return $self->set_layer('transparent') if (@_ == 0);
259:     return $self->set_key('transparent',@_);
260: }
261: sub invisible {
262:     carp &whowasi if $DEBUG;
263:     my $self = shift;
264:     return $self->set_layer('invisible') if (@_ == 0);
265:     return $self->set_key('invisible',@_);
266: }
267: sub hidden {
268:     carp &whowasi if $DEBUG;
269:     my $self = shift;
270:     return $self->set_layer('invisible') if (@_ == 0);
271:     return $self->set_key('invisible',@_);
272: }
273: sub default {
274:     carp &whowasi if $DEBUG;
275:     my $self = shift;
276:     return $self->set_layer('default') if (@_ == 0);
277:     return $self->set_key('default',@_);
278: }

280: #change current layer to a parent
281: #sub reroute {
282: #
283: #}

285: sub id {
286:     carp &whowasi if $DEBUG;
287:     my $self = shift;
288:     $self = tied %$self if ref($self) eq __PACKAGE__;
289:     #my $layer = $self->[$ldc];
290:     return $self->_id;
291: }

293: sub push {
294:     carp &whowasi if $DEBUG;
295:     my $self = shift;
296:     $self = tied %$self if ref($self) eq __PACKAGE__;
297:     my ($ref,$id);
298:     my $l_contents = $self->[$ldc] += 1;
299:     my $l_data = $self->[$ldl];
300:     #clear cache
301:     #$self->[$ilc]->[$ilc_valid] = 0;
302:     
303:     if ($l_contents > $#$l_data) {
304:         push @$l_data, ['a','default'];
305:     } else {
306:         $ref = $$l_data[$l_contents];
307:         $#$ref = 1;
308:         ++$$ref[0];
309:         $$ref[1] = 'default';
310:     }
311:     $id = $self->_id($l_contents); # . $l_data->[$l_contents]->[0];
312:     warn "PUSH:new layer $id\n" if $DEBUG;
313:     return $id;
314: }

316: sub pop {
317:     carp &whowasi if $DEBUG;
318:     my $self = shift;
319:     $self = tied %$self if ref($self) eq __PACKAGE__;
320:     my ($v_stack,$v_layer,$i,$key,$reqid,$curid);
321:     my ($l_current,$l_data,$v_valhash) = @$self[$ldc,$ldl,$vvh];
322:     $reqid = shift;
323:     $curid = $self->_id($l_current); # . $l_data->[$l_current]->[0];
324:     if (defined $reqid && $reqid ne '') {
325:         croak "Attempted to pop layer '$reqid' while at layer '$curid'" unless $reqid eq $curid;
326:     }
327:     croak "Attempted to pop base layer" if $l_current == 0;

329:     #clear cache
330:     #$self->[$ilc]->[$ilc_valid] = 0;

332:     warn "POP:remove layer $curid\n" if $DEBUG;
333:     #need to delete variable stack entries too
334:     $l_data = $$l_data[$l_current];
335:     for (my $i = 2; $i < @$l_data; $i += 1) {
336:         $key = $$l_data[$i];
337:         $v_stack = $$v_valhash{$key};
338:         if (!defined $v_stack || @$v_stack == 0) {
339:             carp "SNH:pop:key '$key' in layer data list has empty stack";
340:             next;
341:         }
342:         $v_layer = $$v_stack[$#$v_stack-1];
343:         confess "SNH:pop:key '$key' in layer data list has most recent layer $v_layer" if $v_layer != $l_current;
344:         #delete value from variable stack, delete empty stacks
345:         if (($#$v_stack -= 2) < 0) {
346:             delete $self->[$vvh]->{$key}; #could leak memory here?
347:         }
348:     }
349:     
350:     #leave structures for reuse
351:     $self->[$ldc] -= 1;
352:     return $curid;
353: }

355: sub shift {
356:     carp &whowasi if $DEBUG;
357:     my $self = CORE::shift;
358:     croak __FILE__.": shift not implemented";
359:     return undef;
360: }

362: sub unshift {
363:     carp &whowasi if $DEBUG;
364:     my $self = CORE::shift;
365:     croak __FILE__.": unshift not implemented";
366:     return undef;
367: }


370: ######################################################################
371: ######################################################################

373: package Hash::Layered::_Internal;
374: use strict;

376: use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION $REVISION); BEGIN {
377:     @ISA = qw( Exporter Tie::Hash );
378:     @EXPORT = ();
379:     @EXPORT_OK = ();
380:     %EXPORT_TAGS = ();
381:     $VERSION = '0.1';
382:     $REVISION = q$Revision: 1.2 $;
383: }
384: use Tie::Hash;
385: use Exporter;

387: use Carp;

389: sub whowasi { (caller(1))[3] . '('.(defined($_[0])?$_[0]:'').')' }
390: sub trace { $DEBUG = @_ ? shift : 1 }

392: #is there an opaque layer between the current and the target layer?
393: #returns the target layer unless a lower layer is opaque
394: #passing >1 in is_write indicates a new key operation
395: sub _intervening_layer {
396:     my ($self,$targ_idx,$is_write) = @_;
397:     $targ_idx = 0 if $targ_idx eq 'top';
398:     my ($layer,$state,$def_state,$def_is_opaque,$def_is_invisible,$layer_idx,
399:         $cache);

401:     #cache no good unless is has separate caches for read/write. sheesh
402:     #$layer_idx = $self->[$ldc];
403:     #lookup target layer in cache, return that value if it exists
404:     #$cache = $self->[$ilc];
405:     #if ($cache->[$ilc_valid]) {
406:     #    $layer = $cache->[$targ_idx + $ilc_offset];
407:     #    return $layer if defined $layer;
408:     #} else {
409:     #    @$cache = ();
410:     #    $cache->[$ilc_valid] = 1;
411:     #}

413:     $def_state = $self->[$dls];
414:     $def_is_opaque = ( ($def_state eq 'opaque') ||
415:                        ($def_state eq 'oneway' && $is_write) ||
416:                        ($def_state eq 'biased' && $is_write > 1) );
417:     $def_is_invisible = ($def_state eq 'invisible');
418:     for ($layer_idx=$self->[$ldc];
419:          $layer_idx > $targ_idx;
420:          $layer_idx -= 1) {
421:         $layer = $self->[$ldl]->[$layer_idx];
422:         $state = $layer->[$ldl_sem];
423:         next if ($state eq 'invisible' ||
424:                  $def_is_invisible && $state eq 'default');
425:         last if  ( ($state eq 'opaque')
426:                    || ( $def_is_opaque && ($state eq 'default') ) );
427:         last if ($is_write && 
428:                  ( ($state eq 'oneway') ||
429:                    ( ($is_write > 1) && ($state eq 'biased') ) ) );
430:     }
431:     $layer_idx = $targ_idx if $layer_idx < $targ_idx;
432:     #$cache->[$targ_idx + $ilc_offset] = $layer_idx;
433:     return $layer_idx;
434: }


437: sub _id {
438:     my $self = shift; my $l = shift;
439:     $l = $self->[$ldc] unless defined $l;
440:     return $l.$self->[$ldl]->[$l]->[0];
441: }

443: sub TIEHASH {
444:     carp &whowasi if $DEBUG;
445:     my $proto = shift;
446:     my $class = ref($proto) || $proto;
447:     my $self = ['' x $obj_size ];
448:     @$self[$dls,$ldc,$ldl,$vvh] = ('biased',-1,[],{});
449:     $$self[$itr] = [[],-1,0];
450:     #$$self[$ldl]->[0] = ['a','default'];
451:     #$self->push;
452:     return bless $self,$class;
453: }

455: sub FETCH {
456:     my $self = shift;
457:     my $key = shift;
458:     carp &whowasi($key) if $DEBUG;
459:     my ($vstack,$var_layer,$var_data,$l);
460:     #lookup variable data
461:     $vstack = $self->[$vvh]->{$key};
462:     return undef unless defined $vstack && @$vstack > 0;
463:     ($var_layer,$var_data) = @$vstack[$#$vstack-1,$#$vstack];
464:     
465:     #check for opaque layer
466:     $l = _intervening_layer($self,$var_layer,0);
467:     warn "FETCH:at "._id($self).":from "._id($self,$l).":".$key."\n" if $DEBUG;
468:     return undef if ($l != $var_layer);
469:     #no check for opaque variable, since it would have to be on top
470:     return $var_data;
471: }

473: #write: 1 => 
474: #       2 => 

476: sub STORE {
477:     my $self = shift;
478:     my $key = shift;
479:     my $val = shift;
480:     carp &whowasi($key) if $DEBUG;
481:     my ($vstack,$targ_layer,$l_idx,$l_data);
482:     #lookup variable data
483:     $vstack = $self->[$vvh]->{$key};
484:     if (!defined $vstack) {
485:         $vstack = $self->[$vvh]->{$key} = [ ];
486:     }
487:     
488:     if (@$vstack == 0) {
489:         $targ_layer = -1;
490:         $l_idx = _intervening_layer($self,'top',1);
491:     } else {
492:         $targ_layer = $$vstack[$#$vstack-1];
493:         $l_idx = _intervening_layer($self,$targ_layer,1);
494:     }
495:     if ($l_idx != $targ_layer) {
496:         #new hash entry
497:         push @$vstack,('','');
498:         $l_idx = _intervening_layer($self,$l_idx,2);
499:         #add variable name to layer data list
500:         $l_data = $self->[$ldl]->[$l_idx];
501:         push @$l_data, $key;
502:     }
503:     warn "STORE:at "._id($self).":to "._id($self,$l_idx).":".$key."\n" if $DEBUG;
504:     @$vstack[$#$vstack-1,$#$vstack] = ($l_idx,$val);
505:     warn "[".join("][",@$vstack)."]\n" if $DEBUG;
506: }

508: #note: pop also does deletion
509: sub DELETE {
510:     my $self = shift;
511:     my $key = shift;
512:     carp &whowasi($key) if $DEBUG;
513:     #lookup variable data
514:     #check for value or opaque layer
515:     #delete value from that layer
516:     my ($vstack,$targ_layer,$l_idx,$l_data,$i);
517:     #lookup variable data
518:     $vstack = $self->[$vvh]->{$key};
519:     if (!defined $vstack || @$vstack == 0) {
520:         return;
521:     }
522:     
523:     $targ_layer = $$vstack[$#$vstack-1];
524:     $l_idx = _intervening_layer($self,$targ_layer,1);
525:     
526:     if ($l_idx != $targ_layer) {
527:         return;
528:     }
529:     #delete value from variable stack, delete empty stacks
530:     if (($#$vstack -= 2) < 0) { #perl could leak memory here
531:         delete $self->[$vvh]->{$key};
532:     }
533:     #TOTEST delete variable name from layer data list
534:     $l_data = $self->[$ldl]->[$l_idx];
535:     for ($i=2; $i < @$l_data; $i += 1) {
536:         last if $$l_data[$i] eq $key;
537:     }
538:     confess "SNH: delete: key [$key] should be on layer $l_idx, but is not in current layer data list" if $i > $#$l_data;
539:     $$l_data[$i] = $$l_data[$#$l_data];
540:     $#$l_data -= 1;
541: }

543: sub EXISTS {
544:     my $self = shift;
545:     my $key = shift;
546:     carp &whowasi($key) if $DEBUG;
547:     my ($vstack,$var_layer,$var_data,$l);
548:     #lookup variable data
549:     $vstack = $self->[$vvh]->{$key};
550:     return undef unless defined $vstack && @$vstack > 0;
551:     ($var_layer,$var_data) = @$vstack[$#$vstack-1,$#$vstack];
552:     #check for opaque layer
553:     #no check for opaque variable, since it would have to be on top
554:     $l = _intervening_layer($self,$var_layer,0);
555:     return ($l == $var_layer);
556: }

558: sub FIRSTKEY {
559:     carp &whowasi if $DEBUG;
560:     my $self = shift;
561:     my $iter = $$self[$itr];
562:     $$iter[0] = [ keys %{ $$self[$vvh] } ];
563:     $$iter[1] = -1;
564:     $$iter[2] = _intervening_layer($self,'top',0);
565:     return $self->NEXTKEY;
566: }

568: sub NEXTKEY {
569:     carp &whowasi if $DEBUG;
570:     my $self = shift;
571:     my $prevkey = shift;
572:     my $iter = $$self[$itr];
573:     my ($vl,$i,$l,$k,$vstack,$var_layer,$var_data);
574:     $vl = $$iter[0];  #var key list
575:     $i = $$iter[1];   
576:     $l = $$iter[2];
577:     #warn "[[@$vl]]:I[$i]:L[$l]\n";
578:     while ($i < $#$vl) {
579:         $i += 1;
580:         $k = $vl->[$i]; #the key
581:         $vstack = $self->[$vvh]->{$k};
582:         if (defined $vstack && @$vstack > 0) {
583:             ($var_layer,$var_data) = @$vstack[$#$vstack-1,$#$vstack];
584:             #warn ":I[$i]:L[$l]:$var_layer,$var_data\n";
585:         } else {
586:             next;
587:         }
588:         last unless ($l > $var_layer);
589:     }
590:     $$iter[1] = $i;
591:     if ($i >= @$vl) {
592:         return ();
593:     } else {
594:         return ($k,$var_data);
595:     }
596: }

598: sub CLEAR {
599:     carp &whowasi if $DEBUG;
600:     my $self = shift;
601:     my ($var_vals,$op_layer,$k,$vstack,$src_layer);
602:     $var_vals = $self->[$vvh];
603:     $op_layer = _intervening_layer($self,'top',1);
604:     foreach $k (keys %$var_vals) {
605:         my ($vstack,$targ_layer,$l_idx);
606:         #lookup variable data
607:         $vstack = $var_vals->{$k};
608:         next if (!defined $vstack || @$vstack == 0);
609:         while ($op_layer <= ($src_layer = $$vstack[$#$vstack-1])) {
610:             $#$vstack -= 2;
611:             last if @$vstack == 0;
612:         }
613:     }
614: }

616: sub DESTROY {
617:     carp &whowasi if $DEBUG;
618: }

620: 1;