Chorus
view release on metacpan or search on metacpan
lib/Chorus/Frame.pm view on Meta::CPAN
_DEFAULT Fallback when _VALUE is absent.
_NEEDED Last-resort coderef called when both _VALUE and _DEFAULT are absent
(backward chaining).
_BEFORE Hook called before a slot value changes.
_AFTER Hook called after a slot value changes (forward propagation).
_ON_DELETE Hook called after a slot is deleted; receives the slot name
(if-removed demon, completing the Minsky triad).
_REQUIRE Validation hook: return REQUIRE_FAILED to block the write.
_NOFRAME Prevents automatic promotion of a nested hash to a frame.
_TERMINAL_SLOTS Arrayref of slot names that must hold a real (_VALUE) value
for the frame to be considered complete (see complete()).
_ALTERNATIVES Arrayref of sibling prototype frames used by fselect()
as an alternative candidate pool (Minsky frame network).
=head2 Inheritance modes N and Z
The global mode controls how C<get()> walks the inheritance chain.
B<Mode N> (default) -- each valuation key is scanned across the whole inheritance tree
before the next key is tried:
_VALUE on (frame, frame._ISA, frame._ISA._ISA, ...)
_DEFAULT on the same tree
_NEEDED on the same tree
B<Mode Z> -- the full sequence C<(_VALUE, _DEFAULT, _NEEDED)> is tried on each frame
before descending to its parents:
frame : _VALUE, _DEFAULT, _NEEDED
frame._ISA: _VALUE, _DEFAULT, _NEEDED ...
Switch with C<< Chorus::Frame::setMode(GET => 'Z') >> or C<< setMode(GET => 'N') >>.
=head2 Exports
C<Chorus::Frame> exports by default:
$SELF Current frame context, updated by get() and set().
&fmatch Slot-based frame selection function.
&fselect Prototype selection by scored slot/value matching (Minsky-style).
&setMode Switches the inheritance mode (N or Z).
REQUIRE_FAILED Constant (-1) returned by _REQUIRE to abort a write.
=cut
BEGIN {
use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
@ISA = qw(Exporter);
@EXPORT = qw($SELF &fmatch &fselect &setMode REQUIRE_FAILED );
@EXPORT_OK = qw();
# %EXPORT_TAGS = ( ); # eg: TAG => [ qw!name1 name2! ];
}
use strict;
use warnings;
use Carp; # warn of errors (from perspective of caller)
use Digest::MD5;
use Scalar::Util qw(weaken);
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 FUNCTIONS
=head2 setMode
Sets the global inheritance mode used by C<get()> to resolve target information
(C<_VALUE>, C<_DEFAULT>, C<_NEEDED>).
Chorus::Frame::setMode(GET => 'N'); # Mode N -- default
Chorus::Frame::setMode(GET => 'Z'); # Mode Z
Chorus::Frame::setMode('N'); # short form
See L</Inheritance modes N and Z> in the DESCRIPTION for a detailed comparison.
=cut
sub setMode {
if (@_ == 1) {
# short form: setMode('Z') or setMode('N')
my $m = uc(shift);
$getMode = MODE_N if $m eq 'N';
$getMode = MODE_Z if $m eq 'Z';
} else {
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
=head2 _keys
Returns the slot names of the frame, excluding the internal keys C<_KEY> and C<_PARENT_KEY>.
my @slots = $frame->_keys;
Use this instead of C<keys %$frame> when you need to iterate over application slots
without exposing internal bookkeeping entries.
=cut
sub _keys {
my ($this) = @_;
grep { $_ ne '_KEY' && $_ ne '_PARENT_KEY' } keys %{$this}; # WARN - frames have EXTRA _KEY and _PARENT_KEY slots !!
}
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
Appends one or more elements to a slot, converting it to an arrayref when necessary.
$frame->_push('tags', 'red', 'big');
=cut
sub _push {
my ($this, $slot, @elems) = @_;
return unless scalar(@elems);
$this->{$slot} = [ defined($this->{$slot}) ? ($this->{$slot}) : () ] unless ref($this->{$slot}) eq 'ARRAY';
push @{$this->{$slot}}, @elems;
}
sub _addInstance {
my ($this, $instance) = @_;
my $k = $instance->{_KEY};
$INSTANCES{$this->{_KEY}}->{$k} = $instance;
# weaken($INSTANCES{$instance); # TOCHECK - does the SAME !!??
#
weaken($INSTANCES{$this->{_KEY}}->{$k}); # Will not increase garbage ref counter to $instance !!
}
=head2 _inherits
Adds one or more parent frames to the inheritance chain outside the constructor.
Each parent is added at most once; duplicates are silently ignored.
$frame->_inherits($parent1, $parent2);
Used internally by C<Chorus::Engine::new()> to wire agent instances to the engine
prototype.
=cut
sub _inherits {
my ($this, @inherited) = @_;
my $k = $this->{_KEY};
for (grep { ! $INSTANCES{$_->{_KEY}}->{$k} } @inherited) { #Â clean list
$_->_addInstance($this); # will use weaken on
$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 _register {
my ($this) = @_;
my $k;
do { $k = Digest::MD5::md5_base64(rand) } while exists($FMAP{$k});
foreach my $slot (keys(%$this)) { # register all slots # not yet _KEY
$REPOSITORY{$slot} = {} unless exists $REPOSITORY{$slot};
$REPOSITORY{$slot}->{$k} = 'Y';
}
$this->{_KEY} = $k; # _KEY SET HERE !!
$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')) { # Warn - Will convert all hash tables to Chorus::Frame -> be carefull with function ref() !!
next if $val->{_NOFRAME};
bless($val, 'Chorus::Frame');
_register($val);
$val->{_PARENT_KEY} //= $_->{_KEY} if _isa($_, 'Chorus::Frame') && exists $_->{_KEY};
_blessToFrameRec($val);
} else {
_blessToFrameRec($_->{$k}) if _isa($val,'ARRAY');
}
if ($k eq '_ISA') {
foreach my $inherited (_isa($val,'ARRAY') ? map { expand($_) || () } @{$val}
: (expand($val))) {
$inherited->_addInstance($_) if $inherited;
}
}
}
return;
}
if (_isa($_,'ARRAY')) {
foreach my $idx (0 .. scalar(@$_) - 1) {
if (_isa($_->[$idx], 'HASH')) {
next if exists $_->[$idx]->{_NOFRAME};
bless($_->[$idx], 'Chorus::Frame');
_register($_->[$idx]);
_blessToFrameRec($_->[$idx]);
} else {
_blessToFrameRec($_->[$idx]) if _isa($_->[$idx],'ARRAY');
}
}
}
}
sub blessToFrame {
my $res = shift;
return $res if _isa($res, 'Chorus::Frame'); # already blessed
SWITCH: {
_isa($res, 'HASH') && do {
return $res if exists $res->{_NOFRAME};
_register(bless($res, 'Chorus::Frame'));
_blessToFrameRec $res if _keys($res); # will ignore _KEY !
last SWITCH;;
};
_isa($res, 'ARRAY') && do {
return $res unless scalar(@$res);
_blessToFrameRec $res;
last SWITCH;
};
( run in 2.441 seconds using v1.01-cache-2.11-cpan-600a1bdf6e4 )