# This is a Perl5 file # ChangeLog # # 2005-09-06 # * new class Perl6::Container::Hash::Native # # 2005-09-05 # * delete hash element # # 2005-08-31 # * New methods: tied(), keys(), values(), pairs(), kv() # * Fixed elems(), buckets() to return boxed Int, Str # * hash iterator (firstkey/nextkey) works # # 2005-08-19 # * New Perl6 class 'Hash' # # 2005-08-18 # * New Perl5 class "Perl6::Container::Hash::Object" # implements a hash in which the keys can be objects # # 2005-08-14 # * added functions clone(), elems(), buckets() # TODO - warn for odd number of elements, on construction # TODO - keys(), values(), pairs(), kv() - lazy (non-infinite) # TODO - each() methods # TODO - hash cells with rw, ro, binding hash elements # TODO - tied hashes # TODO - delete hash slice # TODO - %a = %b - whole hash fetch/store # PIL-Run - %a = { a=>'b', c=>'d' } generates: {({(c, d), (a, b)}, undef)} # TODO - pick() # Does pick remove the element? (Hash and Array) # TODO - (iblech) probably with a warning "uninitialized warning used in numeric contect" # (Same for hashes: %h{undef} =:= %h{""}) # TODO - is (undef=>undef) a valid Pair? # fglock PIL-Run currently prints {('undef', undef)} # buu fglock: For which? # fglock for { undef=>undef } # iblech fglock: Right, I'd think so (and this is how I've implemented it in PIL2JS). # But: By default, hash can autoconvert their keys to Strs, so {undef()=>undef} would have # the same effect as {""=>undef}. But if a hash is declared with shape(Any), {undef()=>undef} # should create a hash with .pairs[0].key being undef # TODO - (for PIL-Run) # buu iblech: Er, let me rephrase. Why did it print an error message? # iblech buu: Because {undef,undef} is not a hash, but a Code, and Pugs tried to coerce # the Code into a Hash, but failed # buu Er, so how do you create a hash ref? # iblech buu: \hash(undef,undef), {undef() => undef}, {pair undef, undef}, etc. # TODO - test - how does a scalar that contains a hash is accessed? # TODO - test $x := %hash - 'undefine $x' # TODO - test %hash := $x - error if $x is not bound to a hash # TODO - tieable hash - cleanup AUTOLOAD # TODO - test 'readonly' # Notes: # * Cell is implemented in the Perl6::Container::Scalar package use strict; use Perl6::MetaModel; use Perl6::Value; use Perl6::Container::Scalar; my $class_description = '-0.0.1-cpan:FGLOCK'; class1 'Hash'.$class_description => { is => [ $::Object ], class => { attrs => [], methods => {} }, instance => { attrs => [ [ '$:cell' => { access => 'rw', build => sub { my $cell = Perl6::Cell->new; my $h = bless {}, 'Perl6::Container::Hash::Object'; $cell->{v} = $h; $cell->{type} = 'Hash'; return $cell; } } ] ], DESTROY => sub { # _('$:cell' => undef); # XXX - MM2.0 gc workaround }, methods => { # %a := %b 'bind' => sub { my ( $self, $thing ) = @_; die "argument to Hash bind() must be a Hash" unless $thing->cell->{type} eq 'Hash'; _('$:cell', $thing->cell); return $self; }, 'cell' => sub { _('$:cell') }, # cell() is used by bind() 'id' => sub { _('$:cell')->{id} }, 'undefine' => sub { _('$:cell')->{v}->clear }, 'tieable' => sub { _('$:cell')->{tieable} != 0 }, 'tie' => sub { shift; _('$:cell')->tie(@_) }, 'untie' => sub { _('$:cell')->untie }, 'tied' => sub { _('$:cell')->{tied} }, # See perl5/Perl6-MetaModel/t/14_AUTOLOAD.t 'isa' => sub { ::next_METHOD() }, 'does' => sub { ::next_METHOD() }, 'ref' => sub { $::CLASS }, 'elems' => sub { Int->new( '$.unboxed' => _('$:cell')->{tied} ? _('$:cell')->{tied}->elems : Perl6::Container::Hash::elems( _('$:cell')->{v} ) ) }, 'buckets' => sub { Str->new( '$.unboxed' => _('$:cell')->{tied} ? _('$:cell')->{tied}->buckets : Perl6::Container::Hash::buckets( _('$:cell')->{v} ) ) }, 'pairs' => sub { my $key = $_[0]->firstkey; my $ary = Array->new; while ( defined $key ) { $ary->push( Pair->new( '$.key' => $key, '$.value' => $_[0]->fetch( $key ) ) ); $key = $_[0]->nextkey; } return $ary; }, 'kv' => sub { my $key = $_[0]->firstkey; my $ary = Array->new; while ( defined $key ) { $ary->push( $key, $_[0]->fetch( $key ) ); $key = $_[0]->nextkey; } return $ary; }, 'keys' => sub { my $key = $_[0]->firstkey; my $ary = Array->new; while ( defined $key ) { $ary->push( $key ); $key = $_[0]->nextkey; } return $ary; }, 'values' => sub { my $key = $_[0]->firstkey; my $ary = Array->new; while ( defined $key ) { $ary->push( $_[0]->fetch( $key ) ); $key = $_[0]->nextkey; } return $ary; }, 'str' => sub { my $key = $_[0]->firstkey; my $value; my @pairs; while ( defined $key ) { $value = $_[0]->fetch( $key ); my $p = Pair->new( '$.key'=>$key, '$.value'=>$value ); push @pairs, $p->str->unboxed; $key = $_[0]->nextkey; } Str->new( '$.unboxed' => '{' . join(', ', @pairs) . '}' ); }, 'perl' => sub { $_[0]->str }, # TODO - XXX - remove this after implementing hash slice 'postcircumfix:{}' => sub { (shift)->fetch( @_ ) }, 'fetch' => sub { my ($self, @param) = @_; #warn "FETCH: @param\n"; my $tmp = _('$:cell')->{tied} ? _('$:cell')->{tied} : _('$:cell')->{v}; my $key = shift @param; if ( Perl6::Value::p6v_isa( $key, 'Array' ) ) { #warn "Hash slice $key\n"; warn "Infinite hash slice not supported\n" if Perl6::Value::numify( $key->is_infinite ); #warn "not implemented"; my $a = Array->new(); for ( 0 .. Perl6::Value::numify( $key->elems ) - 1 ) { my $k = $key->fetch( $_ ); $a->push( $self->fetch( $k ) ); #warn "push $_ - ",Perl6::Value::stringify( $a ),"\n"; #warn "bind $_ -- $k \n"; #$a->fetch( $_ )->bind( $self->fetch( $k ) ); } #warn $a->fetch( 1 ); #warn $self->fetch( 1 ); #$a->fetch( 1 )->bind( $self->fetch( 1 ) ); return $a->slice( 0 .. Perl6::Value::numify( $a->elems ) - 1 ); } my $v = $tmp->fetch( $key ); if ( ! Perl6::Value::p6v_isa( $v, 'Scalar' ) ) { #warn "autovivify - $key - $v\n"; my $s = Scalar->new; $s->store( $v ); $tmp->store( $key, $s ); return $s; } return $v; }, 'store' => sub { my ($self, @param) = @_; my $tmp = _('$:cell')->{tied} ? _('$:cell')->{tied} : _('$:cell')->{v}; if ( scalar @param == 1 ) { # store whole hash if ( Perl6::Value::p6v_isa( $param[0], 'Hash' ) ) { $self->clear; my $key = $param[0]->firstkey; while ( defined $key ) { my $tmp = $param[0]->fetch( $key )->fetch; $self->store( $key, $tmp ); $key = $param[0]->nextkey; } return $self; } if ( Perl6::Value::p6v_isa( $param[0], 'Array' ) ) { $self->clear; for ( 0 .. $param[0]->elems->unboxed - 1 ) { my $pair = $param[0]->fetch( $_ ); $self->store( $pair->key, $pair->value ); } return $self; } if ( Perl6::Value::p6v_isa( $param[0], 'Pair' ) ) { $self->clear; my $pair = $param[0]; $self->store( $pair->key, $pair->value ); return $self; } warn "Don't know how to store @param into a Hash"; } my $key = shift @param; my $s = $self->fetch( $key ); # fetch should always return Scalar if ( ! Perl6::Value::p6v_isa( $tmp, 'Scalar' ) ) { #warn "creating scalar"; $s = Scalar->new; $tmp->store( $key, $s ); } $s->store( @param ); return @param; }, 'AUTOLOAD' => sub { my ($self, @param) = @_; my $method = __('$AUTOLOAD'); # TODO - add support for tied hash # TODO - check if scalar := hash works properly my $tmp = _('$:cell')->{tied} ? _('$:cell')->{tied} : _('$:cell')->{v}; # warn ref($tmp), ' ', $method, " @param == " . $tmp->$method( @param ); return $tmp->$method( @param ); }, }, } }; # ----- unboxed functions package Perl6::Container::Hash::Object; sub store { my ( $this, $key, $value ) = @_; $key = $key->fetch if Perl6::Value::p6v_isa( $key, 'Scalar' ); my $s = Perl6::Value::identify( $key ); $this->{$s} = [ $key, $value ]; return $value; } sub fetch { my ( $this, $key ) = @_; $key = $key->fetch if Perl6::Value::p6v_isa( $key, 'Scalar' ); my $s = Perl6::Value::identify( $key ); $this->{$s}[1]; # warn "fetching " . $this->{$s}[1]; } sub firstkey { my ( $this ) = @_; keys %$this; # force reset the iterator my $s = each %$this; return unless defined $s; $this->{$s}[0]; } sub nextkey { my ( $this, $key ) = @_; my $s = each %$this; return unless defined $s; $this->{$s}[0]; } sub exists { my ( $this, $key ) = @_; $key = $key->fetch if Perl6::Value::p6v_isa( $key, 'Scalar' ); my $s = Perl6::Value::identify( $key ); exists $this->{$s}; } sub delete { my ( $this, $key ) = @_; $key = $key->fetch if Perl6::Value::p6v_isa( $key, 'Scalar' ); my $s = Perl6::Value::identify( $key ); my $r = delete $this->{$s}; $r->[1]; } sub clear { my ( $this ) = @_; %$this = (); } sub scalar { my ( $this ) = @_; 0 + %$this; } package Perl6::Container::Hash::Native; sub new { my $class = shift; # hashref => \%ENV; my %param = @_; $param{hashref} = {} unless defined $param{hashref}; bless { %param }, $class; } sub store { my ( $this, $key, $value ) = @_; my $s = Perl6::Value::identify( $key ); my $v; $v = $value->unboxed if ref( $value ); no warnings 'uninitialized'; $this->{hashref}{$s} = $v; return $value; } sub fetch { my ( $this, $key ) = @_; my $s = Perl6::Value::identify( $key ); $this->{hashref}{$s} # warn "fetching " . $this->{$s}[1]; } sub firstkey { my ( $this ) = @_; keys %{$this->{hashref}}; # force reset the iterator each %{$this->{hashref}}; } sub nextkey { my ( $this, $key ) = @_; each %{$this->{hashref}}; } sub exists { my ( $this, $key ) = @_; my $s = Perl6::Value::identify( $key ); exists $this->{hashref}{$s}; } sub delete { my ( $this, $key ) = @_; my $s = Perl6::Value::identify( $key ); my $r = delete $this->{hashref}{$s}; } sub clear { my ( $this ) = @_; %{$this->{hashref}} = (); } sub scalar { my ( $this ) = @_; 0 + %{$this->{hashref}}; } package Perl6::Container::Hash; sub clone { my $tmp = { %{ $_[0] } }; $tmp; } sub elems { my @tmp = %{ $_[0] }; @tmp / 2 } sub buckets { scalar %{ $_[0] } } 1; __END__ =head1 NAME Perl6::Container::Hash - Perl extension for Perl6 "Hash" class =head1 SYNOPSIS use Perl6::Container::Hash; ... =head1 DESCRIPTION ... =head1 SEE ALSO Pugs =head1 AUTHOR Flavio S. Glock, Efglock@gmail.com =head1 COPYRIGHT AND LICENSE Copyright (C) 2005 by Flavio S. Glock This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.4 or, at your option, any later version of Perl 5 you may have available. =cut