Chorus-Engine
view release on metacpan or search on metacpan
lib/Chorus/Frame.pm view on Meta::CPAN
);
my $f2 = Chorus::Frame->new(
a => {
b1 => sub { $SELF->get('a b2') }, # procedural attachment using context $SELF
b2 => {
_ISA => $f1->{b},
_NEEDED => 'needed for b # needs mode Z to precede inherited _DEFAULT
}
}
);
Chorus::Frame::setMode(GET => 'N');
print $f2->get('a b1') . "\n"; # print 'inherited default for b'
Chorus::Frame::setMode(GET => 'Z');
print $f2->get('a b1') . "\n"; # print 'needed for b'
=cut
=head1 DESCRIPTION
- A frame is a generic object structure described by slots (properties).
- A frame can inherit slots from other frames.
- A frame can have specific slots describing :
* how it can be associated to a target information,
* how he reacts when its target information changes
* what it can try when a missing property is requested.
- The slots _VALUE,_DEFAULT,_NEEDED are tested in this order to obtain the target information
of a given frame (can be inherited).
- Two other special slots _BEFORE & _AFTER can define what a frame has to do before or after
one of its properties changes.
- The slot _ISA is used to define the inheritance.
Two modes 'N' (default) or 'Z' are used to define the priority between a frame and its inherited
frames in order to process its target information
The globale variable $SELF returns the current CONTEXT which is the most recent frame called for the method get().
A slot defined by a function sub { .. } can refer to the current context $SELF in its body.
All frames are automaticaly referenced in a repository used to optimise the selection of frames for a given action.
The function fmatch() can be used to quicly select all the frames responding to a given test on their properties.
=cut
BEGIN {
use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
@ISA = qw(Exporter);
@EXPORT = qw($SELF &fmatch REQUIRE_FAILED);
@EXPORT_OK = qw(%FMAP);
# %EXPORT_TAGS = ( ); # eg: TAG => [ qw!name1 name2! ];
}
use strict;
use Carp; # warn of errors (from perspective of caller)
use Digest::MD5;
use Scalar::Util qw(weaken);
use Data::Dumper;
use constant DEBUG_MEMORY => 0;
use vars qw($AUTOLOAD);
use constant SUCCESS => 1;
use constant FAILED => 0;
use constant REQUIRE_FAILED => -1;
use constant VALUATION_ORDER => ('_VALUE', '_DEFAULT', '_NEEDED');
use constant MODE_N => 1;
use constant MODE_Z => 2;
my $getMode = MODE_N; #Â DEFAULT IS N !!!
my %REPOSITORY;
my %FMAP;
my %INSTANCES;
our $SELF;
my @Heap = ();
sub AUTOLOAD {
my $frame = shift || $SELF;
my $slotName = $AUTOLOAD;
$slotName =~ s/.*://; # strip fully-qualified portion
get($frame, $slotName, @_); # or getN or getZ !!
}
sub _isa {
my ($ref, $str) = @_;
return (ref($ref) eq $str);
}
=head1 SUBROUTINES
=cut
=head2 setMode
Defines the inheritance mode of methods get() for the special slots _VALUE,_DEFAULT,_NEEDED
the default mode is 'N'.
'N' : ex. a single slot from the sequence _VALUE,_DEFAULT,_NEEDED will be tested in all inherited
frames before trying the next one.
'Z' : the whole sequence _VALUE,_DEFAULT,_NEEDED will be tested from the frame before being
tested from the inherited frames
ex. Chorus::Frame::setMode(GET => 'Z');
=cut
sub setMode {
my (%opt) = @_;
$getMode = MODE_N if defined($opt{GET}) and uc($opt{GET}) eq 'N';
$getMode = MODE_Z if defined($opt{GET}) and uc($opt{GET}) eq 'Z';
}
=head1 METHODS
=cut
=head2 _keys
my @k = $f->_keys;
same as CORE::keys but excludes the special slot '_KEY' specific to all frames
=cut
sub _keys {
my ($this) = @_;
grep { $_ ne '_KEY' } keys %{$this};
}
sub pushself {
unshift(@Heap, $SELF) if $SELF;
$SELF = shift;
}
sub popself {
$SELF = shift @Heap;
}
sub expand {
my ($info, @args) = @_;
return expand(&$info(@args)) if _isa($info, 'CODE');
return $info;
}
=head2 _push
push new elements to a given slot (becomes an array if necessary)
=cut
sub _push {
my ($this, $slot, @elems) = @_;
return unless scalar(@elems);
$this->{$slot} = [ $this->{$slot} || () ] unless ref($this->{$slot}) eq 'ARRAY';
unshift @{$this->{$slot}}, @elems;
}
sub _addInstance {
my ($this, $instance) = @_;
my $k = $instance->{_KEY};
$INSTANCES{$this->{_KEY}}->{$k} = $instance;
weaken($INSTANCES{$this->{_KEY}}->{$k}); #Â will not increase garbage ref counter to $instance !!
}
=head2 _inherits
add inherited new frame(s) outside constructor
ex. $f->_inherits($F1,$F2);
=cut
sub _inherits {
my ($this, @inherited) = @_;
my $k = $this->{_KEY};
for (grep { ! $INSTANCES{$_->{_KEY}}->{$k} } @inherited) { #Â clean list
$_->_addInstance($this);
$this->_push('_ISA', $_);
}
return $this;
}
sub _removeInstance {
my ($this, $instance) = @_;
my $k = $instance->{_KEY};
(warn "Instance NOT FOUND !?", return) unless $INSTANCES{$this->{_KEY}}->{$k};
delete $INSTANCES{$this->{_KEY}}->{$k};
}
sub blessToFrame {
sub register {
my ($this) = @_;
my $k;
do {
$k = Digest::MD5::md5_base64( rand );
} while(exists($FMAP{$k}));
foreach my $slot (keys(%$this)) { # register all slots
$REPOSITORY{$slot} = {} unless exists $REPOSITORY{$slot};
$REPOSITORY{$slot}->{$k} = 'Y';
}
$this->{_KEY} = $k;
$FMAP{$k} = $this;
weaken($FMAP{$k}); #Â will not increase garbage ref counter to $this !!
return $this;
}
sub blessToFrameRec {
local $_ = shift;
if (_isa($_,'Chorus::Frame')) {
while(my ($k, $val) = each %$_) {
if (_isa($val,'HASH')) {
next if $val->{_NOFRAME};
bless($val, 'Chorus::Frame');
$val->register();
blessToFrameRec($val);
} else {
if (_isa($val,'ARRAY')) {
blessToFrameRec($_->{$k});
}
}
if ($k eq '_ISA') {
foreach my $inherited (_isa($val,'ARRAY') ? map \&expand, @{$val}
: (expand($val))) {
$inherited->_addInstance($_) if $inherited;
}
}
}
return;
}
if (_isa($_,'ARRAY')) { # Ã revoir (sans $idx)
foreach my $idx (0 .. scalar(@$_) - 1) {
if (_isa($_[$idx], 'HASH')) {
next if exists $_[$idx]->{_NOFRAME};
bless($_[$idx], 'Chorus::Frame');
$_[$idx]->register();
blessToFrameRec($_[$idx]);
} else {
if (_isa($_[$idx],'ARRAY')) {
blessToFrameRec($_[$idx]);
}
}
}
}
}
my $res = shift;
return $res if _isa($res, 'Chorus::Frame'); # already blessed
SWITCH: {
_isa($res, 'HASH') && do {
return $res if exists $res->{_NOFRAME};
bless($res, 'Chorus::Frame')->register();
blessToFrameRec $res if keys(%$res);
last SWITCH;;
};
( run in 1.123 second using v1.01-cache-2.11-cpan-39bf76dae61 )