#! /usr/local/bin/perl -w # NOTES: # Invariants and pre- and post-conditions are expected # to return undef if they fail. # # Pre- and post-conditions receive the same argument list # as the implementation itself. Methods and constructors # may have as many pre- and post-conditions as they # require. # # Pre- and post-conditions and invariants may be declared # optional. Optional conditions may be switched on and off # using the &check method (see examples below). # # The subroutine &self always returns a reference to # the invoking object. However, that reference is still # also passed as the first argument. # # The implementation's return value is available in the # method's post-condition(s) through the subroutine # &value, which returns a reference to a scalar or an array # (depending on the calling context). # # &value also provides access to the value of an attribute within # that attribute's pre- and post-conditions. # # The value of the object prior to a method is available in the # post-conditions via the &old subroutine, which returns a copy # of the object as it was prior to the method call. # # Methods can be declared abstract. They croak if not redefined. # # Class methods and attributes can be declared. # # The constructor implementation is invoked *after* the object # is created and blessed into the class. It only needs to # initialize the object returned by &self. Its return value is ignored. # # The implementations of all base class constructors are called # automatically by the derived class constructor (and passed # the same argument list) # # Attributes are private to the class in which they're declared. # Attributes cannot be accessed directly, only via their # accessor methods. This is true even within class methods. # All generated accessors return a reference to their attribute. # # Accessors may only have preconditions. # # Accessors and methods inherit (all) the preconditions of # every ancestral accessor or method of the same name. # package QueueBase; use Class::Contract 'old'; contract { abstract method 'append'; abstract method 'next'; ctor 'new'; impl { print "QueueBase::new!\n" }; }; package ClientQueue; use Class::Contract 'old'; contract { inherits QueueBase; invar { print "appends: ", self->flags->{append} || 0, "\n"; }; invar { print "nexts: ", self->flags->{next} || 0, "\n"; }; optional invar { @{self->queue} > 0 || undef; }; failmsg "Empty queue detected at %s after call"; attr queue => ARRAY; attr flags => HASH; class attr 'first'; method 'append'; optional pre { print "first append\n" if ${self->first}; 1; }; pre { print "<<<0>>>\n"; return 0 unless shift(@_)->isa("Client"); print "<<<0.1>>>\n"; 1; }; failmsg "Expected Client object"; post { return unless @{self->queue} == @{old->queue} + 1; return unless self->queue->[-1]{id} == $_[0]{id}; return 1; }; impl { print "<<<1>>>\n"; ${self->first} = 0; print "<<<2>>>\n"; self()->flags->{append}++; print "<<<3>>>\n"; push @{self->queue}, shift; print "<<<4>>>\n"; }; method 'next'; post { return unless @{self->queue} == @{old->queue} - 1; return 1; }; failmsg "Expected removal of a single Client object"; impl { self->flags->{next}++; shift @{self->queue} }; ctor 'new'; pre { return unless @_ >= 1 && !grep {!$_->isa('Client')} @_; return 1; }; failmsg "constructor must be passed an initial Client obj"; impl { @{self->queue} = ( shift ); ${self->first} = 1; }; }; package OrderedQueue; use Class::Contract 'old'; contract { inherits 'ClientQueue'; method 'append'; post { return unless $_[0]{id} > self->queue->[-2]{id}; }; failmsg "Client appended out of order"; ctor 'new'; impl { print "OrderedQueue::new!\n" }; }; package Client; my $nextid = 1; sub new { bless { id => $nextid++ }, ref($_[0]) || $_[0]; } package Main; use Class::Contract qw(check); check my %contract => 0 for (__ALL__); # TURN OFF ALL OPTIONAL CHECKS check %contract for ('ClientQueue'); # TURN ON OPTIONAL CHECKS # FOR ClientQueue ONLY print "[[[1]]]\n"; my $client = Client->new(); print "[[[2]]]\n"; my $order_queue = OrderedQueue->new($client); $client = Client->new(); print "[[[3]]]\n"; $order_queue->append($client); print "[[[4]]]\n"; $client = Client->new(); my $client2 = Client->new(); print "[[[5]]]\n"; # Uncomment following to get append out of order error # $order_queue->append($client2); $order_queue->append($client); print "[[[6]]]\n"; $client = "not a client"; # Expected Client object eval '$order_queue->append($client)'; print $@ if $@; print $order_queue->next(), "\n"; print $order_queue->next(), "\n"; print $order_queue->next(), "\n"; # Nothing left in queue: Expected removal a single Client object my $val = $order_queue->next(); print "$val\n"; 1;