#!/usr/bin/perl # ChangeLog # # 2005-09-03 # * Fixed Code-do() return context # # 2005-09-01 # * New Class Perl6::Type # * Signature can be stringified - new methods Code::signature_str and Perl6::Param::str # # 2005-08-22 # * bugfix - MultiSub wasn't checking parameter signature # * @_ is bound - '.say' works! # * Added 'slurpy' parameter # * Compatible with Perl 5 scalar/list context # * Removed classes Params, Signature # * Added 'default' parameter hook # * Added required/optional parameters # * Param 'type' is a closure, allowing the use of 'subtype' # * New methods: .arity, .name # * Finished migration to Object model # # 2005-08-21 # * Refactored Code.pm from MetaModel t/80_Code.t # * 'Code', 'Sub', 'MultiSub', 'Block' are now Perl6 classes # Notes: # Subroutine global names are not created by the Code object. # - code.t stores names in %Perl6::[Multi]Sub::SUBS # Algorithm for matching/binding: # extract all +$x, ?+$x - must be Pairs # extract all positionals, counting (but skipping) Pairs. extract slurp # extract remaining Pairs # $^a, $^b count like '+' # MORE TODO - See "S06" # TODO - Perl6::Param - PIL gives these extra attributes: # is_invocant - ??? # is_writable # is_lazy - ??? # is_lvalue - ??? # # Note: paramContext is an object that contains the 'type' # 'Context' can be Slurpy or Item # TODO - Code - PIL gives these extra attributes: # is_lvalue # TODO - slurpy parameter spec should generate a boxed Array or Hash # TODO - XXX - in Code->do() - @ret should be a boxed Array # TODO - add want() data # TODO - add caller() data # TODO - junctive types # TODO - does 'returns' calls coerce: ? # TODO - the 'match' argument to Perl6::Type->new() should be a P6 'Block' instance # TODO - 'Pad' structure # TODO - create base "types" and reuse - see Perl6::Param # TODO - name_required() parameter to Perl6::Param signature ( +$x ) # TODO - 'returns' # TODO - unify parameter types and binding with MetaModel's methods/multimethods # TODO - multisub add_sub($sub) # TODO - multisub .arity, .name # TODO - escape continuations ? # TODO - modify constant parameter is an error # TODO - splat can't be rw # TODO - examples - subname, tail recursion, caller # TODO - return lvalue; Proxy # TODO - multisubs are checking parameters twice # TODO - parameter types - is rw, is copy, is ref, is context(Scalar) # TODO - slurpy Code *$block # TODO - caller context - want() # TODO - test the integration with Value and Container types # TODO - add hooks for signature checks, autoboxing/un-boxing, return value checking and autoboxing/un-boxing # TODO - add support for optional parameters ?$x, pairs # TODO - say { @_ } - test it with Int->new(3)->say # TODO - Coro # TODO - implement infinite list parameter - (1..Inf).shift # TODO - box-up .arity and .name return values use strict; use warnings; use Carp 'confess'; use Perl6::MetaModel; use Perl6::Value; my $class_description = '-0.0.1-cpan:FGLOCK'; { package Perl6::Type; sub new { # TODO - autobox/un-box hook my ($class, %param ) = @_; my ( $name, $match, $supertype ) = ( $param{name}, $param{match}, $param{supertype} ); bless { name => $name, match => $match, supertype => $supertype, } => $class; } sub name { $_[0]{name} } sub match { return 0 if defined $_[0]{supertype} && ( ref($_[1]) ? ! $_[0]{supertype}{match}( $_[1] ) : ! $_[0]{supertype}{match}( \$_[1] ) ); ref($_[1]) ? $_[0]{match}( $_[1] ) : $_[0]{match}( \$_[1] ) } } { package Perl6::Param; sub new { # TODO - autobox/un-box hook # NOTE - default is a closure my ($class, %param ) = @_; my ( $type, $name, $default, $required, $slurpy ) = ( $param{type}, $param{name}, $param{default}, $param{required}, $param{slurpy} ); $default = sub {} unless defined $default; my $optional = $name =~ s/^.?\?//; $required = defined $required ? $required : ! $optional; my $slurp = $name =~ s/^.?\*//; $slurpy = defined $slurpy ? $slurpy : $slurp; $type = defined $type ? $type : $name =~ /^\$/ ? Perl6::Type->new( name => 'Any', match => sub { 1 } ) : $name =~ /^\@/ ? Perl6::Type->new( name => 'Array', match => sub { ref($_[0]) eq 'ARRAY' || ref($_[0]) eq 'Array' || ref($_[0]) eq 'Dispatchable' && $_[0]->isa('Array') } ) : $name =~ /^\%/ ? Perl6::Type->new( name => 'Hash', match => sub { ref($_[0]) eq 'HASH' || ref($_[0]) eq 'Hash' || ref($_[0]) eq 'Dispatchable' && $_[0]->isa('Hash') } ) : $name =~ /^\&/ ? Perl6::Type->new( name => 'Any', # XXX - should be Sub or Code or something match => sub { 1 } ) : die 'Sigil required in the parameter name ('.$name.')'; bless { name => $name, type => $type, default => $default, required => $required, slurpy => $slurpy, } => $class; } sub name { $_[0]{name} } sub default { $_[0]{default}() } sub required { $_[0]{required} } sub optional { ! $_[0]{required} } sub slurpy { $_[0]{slurpy} } sub type { $_[0]{type} } sub match_type { ref($_[1]) ? $_[0]{type}->match( $_[1] ) : $_[0]{type}->match( \$_[1] ) } sub str { my $s = ''; $s .= $_[0]{type}->name . ' '; $s .= '*' if $_[0]->slurpy; $s .= '?' if $_[0]->optional; # $s .= '+' if $_[0]->name_required; -- not used in PIL-v1 $s .= $_[0]->name; $s .= ' = ' . Perl6::Value::stringify( $_[0]->default ) if $_[0]->default; return $s; } } class1 'Code'.$class_description => { is => [ $::Object ], class => { attrs => [], methods => {} }, instance => { attrs => [ '$.body', '$.params', '$.name', '$.returns' ], DESTROY => sub {}, methods => { # Currying 'assuming' => sub { my ( $self, @args ) = @_; #warn "PREBIND $self => @args"; return Code->new( '$.body' => sub { my $obj = shift; #warn "PREBOUND @args, @_"; return $self->do( @args, @_ ); }, '$.name' => $self->name . '_Curried_', '$.params' => [ Perl6::Param->new( 'name' => '*@_' ) ], '$.returns' => undef, ); }, # TODO #'num' => sub { Num->new( '$.unboxed' => _('$.unboxed')->num ) }, #'int' => sub { Int->new( '$.unboxed' => _('$.unboxed')->int ) }, 'str' => sub { $_[0]->perl }, 'bit' => sub { Bit->new( '$.unboxed' => 1 ) }, 'perl' => sub { #my $s = "sub "; my $s = lc( $::CLASS->name ) . " "; $s .= Perl6::Value::stringify( _('$.name') ) . " " if defined _('$.name'); $s .= "(" . $_[0]->signature_str . ") {...}"; return Str->new( '$.unboxed' => $s ) }, 'ref' => sub { $::CLASS }, 'defined' => sub { Bit->new( '$.unboxed' => 1 ) }, 'expand_junctions' => sub { my ($self, @arguments) = @_; #warn "DO @arguments\n"; # is there a junction in the arg list? for my $i ( 0 .. $#arguments ) { my $arg = $arguments[$i]; if ( ref($arg) && $arg->isa( 'Junction' ) ) { #warn "HAS JUNCTION ", $arg->str->unboxed; my @items = @{$arg->things}; #warn "ITEMS @items = @{[ map { Perl6::Value::stringify($_) } @items ]}"; my @r; for my $element ( @items ) { my @a = @arguments; $a[$i] = $element; push @r, $self->do( @a ); } #warn "RETURNING @r = @{[ map { Perl6::Value::stringify($_) } @items ]}"; my $j = Junction->new; $j->type( $arg->type ); $j->things( \@r ); $j->junction_normalize; return $j; } } return; }, do => sub { my ($self, @arguments) = @_; my $j = $self->expand_junctions( @arguments ); return $j if $j; my($ok,$err)=$self->check_params(@arguments); $ok || confess "Signature does not match - (" . $self->signature_str . "): $err"; # my %bound_params = $::SELF->bind_params(@arguments); # warn "entering sub ".$self->name; my ($ret) = $self->body->( $self, @arguments ); # @_ = self + raw arguments # warn "RETURN $ret\n"; # @ret is a boxed Array warn "Return type does not match - should return " . $self->returns->name if defined $self->returns && ! $self->returns->match( $ret ); return $ret; }, arity => sub { scalar @{ $::SELF->params } }, signature_str => sub { my $self = shift; my @s; for (my $i = 0; $i < @{ $self->params }; $i++) { my $spec = ${ $self->params }[$i]; push @s, $spec->str; } return join( ', ', @s ); }, check_params => sub { my ($self, @params) = @_; # my $max = ( scalar @params > scalar @{ $self->params } ) ? scalar @params : @{ $self->params }; my $i = 0; my $num_required = 0; # warn "params ". @{ $self->params } . " -- @params"; while(1) { if ( $i >= scalar @{ $self->params } && $i >= scalar @params ) { #warn "CHECK: $i - required $num_required - given @params"; return (0,"err1") if $num_required > scalar @params; return 1; } # return 1 if $i > scalar $#{ $self->params } && $i > scalar $#params; return (0,"err2") if $i > scalar $#{ $self->params } && $i <= scalar $#params; # warn $i; my $spec = ${ $self->params }[$i]; my $candidate = $params[$i]; $num_required++ if $spec->required; next if $spec->optional && ! defined $candidate; if ( $spec->slurpy ) { ## && $spec->name eq 'Array' ) { ## return (0,"") unless $spec->match_type( [ @params[$i..$#params] ] ); XXX return 1; # @params = (); } else { my $mt = $spec->match_type($candidate); return (0,"type mismatch: $candidate vs ".$spec->str) if !$mt; } } continue { $i++ } return 1; }, bind_params => sub { my ($self, @params) = @_; my %bound_params; for (my $i = 0; $i < @{ $self->params }; $i++) { my $spec = ${ $self->params }[$i]; my $candidate = $params[$i]; $candidate = $spec->default if $spec->optional && ! defined $candidate; if ( $spec->slurpy ) { if ( $spec->type->name eq 'Array' ) { my $ary = Array->new; $ary->push( @params[$i..$#params] ); $bound_params{ ${$self->params}[$i]->name } = $ary; # $bound_params{ ${$self->params}[$i]->name } = [ @params[$i..$#params] ]; } elsif ( $spec->type->name eq 'Hash' ) { my $ary = Hash->new; for ( @params[$i..$#params] ) { my $key = $_->key; my $value = $_->value; $ary->store( $key, $value ) } $bound_params{ ${$self->params}[$i]->name } = $ary; # $bound_params{ ${$self->params}[$i]->name } = [ @params[$i..$#params] ]; } else { warn "Don't know how to handle slurpy ".$spec->name; } @params = (); } else { $bound_params{ ${$self->params}[$i]->name } = $candidate; } } return %bound_params; }, }, } }; class1 'Sub'.$class_description => { is => [ 'Code', $::Object ], class => {}, instance => { attrs => [], methods => {}, }, }; class1 'MultiSub'.$class_description => { is => [ 'Code', $::Object ], class => {}, instance => { attrs => [ '@.subs' ], # , '$.return_value' ], methods => { do => sub { my ($self, @args) = @_; my $j = $self->expand_junctions( @args ); return $j if $j; # warn "testing multisub, ".( scalar @{ _('@.subs') } ); foreach my $_sub ( @{ _('@.subs') } ) { if ( $_sub->check_params( @args ) ) { return $_sub->do(@args); } } die 'No compatible MultiSub'; }, }, }, }; class1 'Block'.$class_description => { is => [ 'Code', $::Object ], class => {}, instance => {}, }; 1; __END__ =pod =head1 AUTHOR Original code and tests by Stevan Little Estevan@iinteractive.comE The code was refactored into it's own Class from MetaModel t/80_Code.t by Flavio S. Glock Efglock@gmail.comE =cut