# # - Value classes # Num, Int, Str, Bit, Pair, Ref, List # # - functions for implementation of unboxed Perl6 Values in Perl5 # Perl6::Value::Num - Inf, NaN # Perl6::Value::Int # Perl6::Value::Str # Perl6::Value::Bit - bool::* taint::* # Perl6::Value::List (separate file) # ChangeLog # # 2005-10-03 # * Fixed Pair stringification ("$key\t$value") and .perlification # ("($key.perl() => $value.perl())") # # 2005-09-27 # * Str.decrement() works # * Migrated to MetaModel-2 (putter) # # 2005-09-03 # * Fixed Pair stringification # # 2005-09-01 # * Added support for unboxed Pair.key, Pair.value # # 2005-08-18 # * clean-up Ref implementation - uses AUTOLOAD to auto-deref # * added unboxed enums: bool::* taint::* # * 'List' clean up - removed 'multisub' methods # # 2005-08-17 # * added boxed type: List (lazy, non-lazy, infinite) # * More descriptive method names: # - .unboxed() - returns the unboxed value # Pair->unboxed returns two objects. # - Ref->referred - returns the referred object # - removed method .value() - except for Pair # # 2005-08-16 # * 'Scalar' temporarily implemented here # * added class Ref # * added method .perl() # # 2005-08-15 # * added boxed types: Num, Int, Str, Bit, Pair # # 2005-08-13 # * refactored from Perl6::Value::List # Notes: # - All library functions - add, subtract, sqrt, ... # are implemented using multisubs # - 'Ref' do not auto-deref List or any other Value - only Containers # TODO - implement tests from t/var/autoderef.t # TODO - constant # TODO - move .increment, .decrement to Primitives (this will break some tests) # TODO - move Num::Inf, Num::NaN to Primitives use strict; sub Perl6::Value::p6v_isa { my($o,$cls)=@_; my $ref = ref($o); return 1 if $ref eq 'Dispatchable' && $o->isa($cls); return 0; } use Perl6::MetaModel; sub class1 { my ($name, $params) = @_; my $barename = $name; $barename =~ s/-0.*//; my $code=""; $code .= "package $barename;\n"; $code .= "our \$MM = \$::Class->new('\$:name' => '$barename');\n"; $code .= "sub new { shift; \$MM->new(\@_) }\n"; #$code .= "sub new { my \$c = shift; my \$o = \$MM->new(\@_); warn \"new: \$c \$o\"; \$o; }\n"; $code .= "\$MM;"; my $MM = eval($code); die "$@\n$code" if $@; for my $key (keys %$params) { my $val = $params->{$key}; if($key eq 'is') { my $sc = [map{ref($_)?$_:do{no strict;my $n=$_."::MM";$$n}}@$val]; #print STDERR "$barename @$sc\n"; $MM->superclasses($sc); } elsif($key eq 'class') { for my $k (keys %$val) { my $v = $val->{$k}; if($k eq 'methods') { foreach my $m (keys %$v) { #warn "$barename $m\n"; $MM->add_method($m => ::make_class_method($v->{$m})); } } } } elsif($key eq 'instance') { for my $k (keys %$val) { my $v = $val->{$k}; if($k eq 'attrs') { #print STDERR "$barename @$v\n"; foreach my $a (@$v) { my $a_config = {}; if(ref($a)) { $a_config = $a->[1]; $a = $a->[0]; } my $am = $a; $am =~ s/\$.//; $MM->add_attribute($a => ::make_attribute($a)); my $access = $a_config->{'access'}; my $build = $a_config->{'build'}; my $is_rw = $access && $access eq 'rw'; # XXX - everything treated as rw; $MM->add_method($am => ::make_method(sub { my $self = shift; ::opaque_instance_attr($self => $a) = shift if @_; ::opaque_instance_attr($self => $a); })); if($build) { $MM->add_method('BUILD' => ::make_submethod(sub{ shift; _($a => $build->()); })); } } } elsif($k eq 'DESTROY') { $MM->add_method('DESTROY' => ::make_method(sub{ shift; $v->(); })); } elsif($k eq 'methods') { foreach my $m (keys %$v) { #warn "$barename $m\n"; $MM->add_method($m => ::make_method($v->{$m})); } } } } } $MM; } my $class_description = '-0.0.1-cpan:FGLOCK'; class1 'Num'.$class_description => { is => [ $::Object ], class => { attrs => [], methods => { # TODO - pi ? 'Inf' => sub { my ($class) = @_; return $class->new( '$.unboxed' => &Perl6::Value::Num::Inf ); }, 'NaN' => sub { my ($class) = @_; return $class->new( '$.unboxed' => &Perl6::Value::Num::NaN ); }, } }, instance => { attrs => [ '$.unboxed' ], DESTROY => sub {}, methods => { 'num' => sub { $::SELF }, 'int' => sub { Int->new( '$.unboxed' => Perl6::Value::Num::to_int( _('$.unboxed') ) ) }, 'str' => sub { Str->new( '$.unboxed' => Perl6::Value::Num::to_str( _('$.unboxed') ) ) }, 'bit' => sub { Bit->new( '$.unboxed' => Perl6::Value::Num::to_bit( _('$.unboxed') ) ) }, 'perl' => sub { $::SELF->str }, 'increment' => sub { my $value = _('$.unboxed'); $::SELF->ref->new( '$.unboxed' => ++$value ) }, 'decrement' => sub { my $value = _('$.unboxed'); $::SELF->ref->new( '$.unboxed' => --$value ) }, 'ref' => sub { $::CLASS }, }, } }; class1 'Int'.$class_description => { is => [ $::Object ], class => { attrs => [], methods => {} }, instance => { attrs => [ '$.unboxed' ], DESTROY => sub {}, methods => { 'num' => sub { Num->new( '$.unboxed' => Perl6::Value::Int::to_num( _('$.unboxed') ) ) }, 'int' => sub { $::SELF }, 'str' => sub { Str->new( '$.unboxed' => Perl6::Value::Int::to_str( _('$.unboxed') ) ) }, 'bit' => sub { Bit->new( '$.unboxed' => Perl6::Value::Int::to_bit( _('$.unboxed') ) ) }, 'perl' => sub { $::SELF->str }, 'increment' => sub { my $value = _('$.unboxed'); $::SELF->ref->new( '$.unboxed' => ++$value ) }, 'decrement' => sub { my $value = _('$.unboxed'); $::SELF->ref->new( '$.unboxed' => --$value ) }, 'ref' => sub { $::CLASS }, }, } }; class1 'Str'.$class_description => { is => [ $::Object ], class => { attrs => [], methods => {} }, instance => { attrs => [ '$.unboxed' ], DESTROY => sub {}, methods => { 'num' => sub { Num->new( '$.unboxed' => Perl6::Value::Str::to_num( _('$.unboxed') ) ) }, 'int' => sub { Int->new( '$.unboxed' => Perl6::Value::Str::to_int( _('$.unboxed') ) ) }, 'str' => sub { $::SELF }, 'bit' => sub { Bit->new( '$.unboxed' => Perl6::Value::Str::to_bit( _('$.unboxed') ) ) }, 'perl' => sub { my $tmp = _('$.unboxed'); $tmp =~ s/(\\|\')/\\$1/g; # quote ' \ return Str->new( '$.unboxed' => "'" . $tmp . "'" ); }, 'increment' => sub { my $value = _('$.unboxed'); $::SELF->ref->new( '$.unboxed' => ++$value ) }, 'decrement' => sub { my $value = _('$.unboxed'); # perl5 doesn't support string decrement $::SELF->ref->new( '$.unboxed' => Perl6::Value::Str::decrement( $value ) ) }, 'ref' => sub { $::CLASS }, }, } }; class1 'Bit'.$class_description => { is => [ $::Object ], class => { attrs => [], methods => {} }, instance => { attrs => [ '$.unboxed' ], DESTROY => sub {}, methods => { 'num' => sub { Num->new( '$.unboxed' => Perl6::Value::Bit::to_num( _('$.unboxed') ) ) }, 'int' => sub { Int->new( '$.unboxed' => Perl6::Value::Bit::to_int( _('$.unboxed') ) ) }, 'str' => sub { Str->new( '$.unboxed' => Perl6::Value::Bit::to_str( _('$.unboxed') ) ) }, 'bit' => sub { $::SELF }, 'perl' => sub { $::SELF->str }, 'ref' => sub { $::CLASS }, }, } }; class1 'Rat'.$class_description => { is => [ $::Object ], class => { attrs => [], methods => {}, }, instance => { attrs => [ '$.a', '$.b' ], DESTROY => sub {}, methods => { 'num' => sub { Num->new( '$.unboxed' => _('$.a')/_('$.b') ) }, 'int' => sub { Int->new( '$.unboxed' => Perl6::Value::Num::to_int( _('$.a')/_('$.b') ) ) }, 'str' => sub { Str->new( '$.unboxed' => Perl6::Value::Num::to_str( _('$.a')/_('$.b') ) ) }, 'bit' => sub { Bit->new( '$.unboxed' => Perl6::Value::Num::to_bit( _('$.a')/_('$.b') ) ) }, 'perl' => sub { $::SELF->str }, 'ref' => sub { $::CLASS }, }, } }; class1 'Pair'.$class_description => { is => [ $::Object ], class => { attrs => [], methods => {} }, instance => { attrs => [ '$.key', '$.value' ], # [ '$.value' => { access => 'rw' } ] ], DESTROY => sub {}, methods => { 'num' => sub { Num->new( '$.unboxed' => 0 ) }, 'int' => sub { Int->new( '$.unboxed' => 0 ) }, 'str' => sub { my $key = Perl6::Value::stringify( _('$.key') ); my $value = Perl6::Value::stringify( _('$.value') ); Str->new( '$.unboxed' => "$key\t$value" ) }, 'bit' => sub { Bit->new( '$.unboxed' => 0 ) }, 'perl' => sub { my $self = shift; my $key = Perl6::Value::stringify( $self->key->perl ); my $value = Perl6::Value::stringify( $self->value->perl ); Str->new( '$.unboxed' => "($key => $value)" ) }, 'unboxed' => sub { ( _('$.key'), _('$.value') ) }, 'ref' => sub { $::CLASS }, 'isa' => sub { ::next_METHOD() }, 'does' => sub { ::next_METHOD() }, 'AUTOLOAD' => sub { my ($self, @param) = @_; my $method = __('$AUTOLOAD'); die "unsupported pair method .$method"; }, }, } }; class1 'Ref'.$class_description => { is => [ $::Object ], class => { attrs => [], methods => {} }, instance => { attrs => [ '$.referred' ], DESTROY => sub { # _('$.referred' => undef); # XXX - MM2.0 gc workaround }, methods => { # See perl5/Perl6-MetaModel/t/14_AUTOLOAD.t 'isa' => sub { return 1 if $_[1] eq 'Array' && Perl6::Value::p6v_isa( _('$.referred'), 'Array' ); return 1 if $_[1] eq 'Hash' && Perl6::Value::p6v_isa( _('$.referred'), 'Hash' ); ::next_METHOD(); }, 'does' => sub { ::next_METHOD() }, 'unboxed' => sub { \(_('$.referred')) }, 'AUTOLOAD' => sub { my ($self, @param) = @_; my $method = __('$AUTOLOAD'); my $tmp = _('$.referred'); # Array and Hash are auto-dereferenced if ( ref $tmp && ( $tmp->isa( 'Array' ) || $tmp->isa( 'Hash' ) ) ) { return $tmp->$method( @param ); } # everything else is not auto-dereferenced return Bit->new( '$.unboxed' => 1 ) if $method eq 'bit'; return $::CLASS if $method eq 'ref'; if ( $method eq 'perl' || $method eq 'str' ) { return Str->new( '$.unboxed' => '\\' . Perl6::Value::stringify($tmp) ) } return Bit->new( '$.unboxed' => defined $tmp ? 1 : 0 ) if $method eq 'defined'; die "unsupported ref method .$method"; }, }, } }; role Lazy => { methods => {} }; role Eager => { methods => {} }; class1 'List'.$class_description => { is => [ $::Object ], class => { attrs => [], methods => {} }, instance => { attrs => [ '$.unboxed' ], DESTROY => sub {}, methods => { 'num' => sub { Num->new( '$.unboxed' => _('$.unboxed')->num ) }, 'int' => sub { Int->new( '$.unboxed' => _('$.unboxed')->int ) }, 'str' => sub { Str->new( '$.unboxed' => _('$.unboxed')->str ) }, 'bit' => sub { Bit->new( '$.unboxed' => _('$.unboxed')->bit ) }, 'perl' => sub { Str->new( '$.unboxed' => _('$.unboxed')->perl ) }, 'ref' => sub { $::CLASS }, 'shift' => sub { _('$.unboxed')->shift }, 'pop' => sub { _('$.unboxed')->pop }, # These methods are implemented in List.pm but are not exposed here, # they are going to be moved to Primitives: # elems, is_infinite, is_lazy, is_contiguous, flatten, grep, ... }, } }; # ---------- Implementation of unboxed values -------------- #package Perl6::Value; # #sub to_ref { # my $object = shift; # my $class_name = ::dispatch( ::meta( $object ), 'name' ); # ** get the class name, from object # my $class = $Perl6::Class::ALL_CLASSES{$class_name}; # ** get the class, from class name # return $class; #} $Perl6::Value::obj_id = '**ObJecT**' . rand; sub Perl6::Value::identify { my $key = shift; return $Perl6::Value::obj_id unless defined $key; my $s = $key; if ( ref($key) && ( $key->isa( 'Int' ) || $key->isa( 'Num' ) || $key->isa( 'Str' ) || $key->isa( 'Bit' ) || $key->isa( 'Rat' ) ) ) { $s = $key->str->unboxed } elsif ( UNIVERSAL::can( $key, 'id' ) ) { $s = $Perl6::Value::obj_id . $key->id } return $s; } sub Perl6::Value::stringify { my $s = shift; $s = $s->fetch if Perl6::Value::p6v_isa( $s, 'Scalar'); my $tmp; # warn "stringify - $s\n"; eval { $tmp = $s->str(max=>3) }; $s = $tmp unless $@; # warn " str - $s - $@\n"; eval { $tmp = $s->unboxed }; $s = $tmp unless $@; # warn " unboxed - $s - $@\n"; return 'undef' unless defined $s; no warnings 'numeric'; $s = Perl6::Value::Num::to_str( $s ) if $s+0 eq $s; return $s; } sub Perl6::Value::numify { my $s = shift; $s = $s->fetch if Perl6::Value::p6v_isa( $s, 'Scalar'); my $tmp; eval { $tmp = $s->num }; $s = $tmp unless $@; eval { $tmp = $s->unboxed }; $s = $tmp unless $@; unless ( defined $s ) { warn "attempting to use 'undef' as a number"; return 0; } no warnings 'numeric'; $s = Perl6::Value::Str::to_num( $s ) if $s+0 ne $s; return $s; } package Perl6::Value::Num; use constant Inf => 100**100**100; use constant NaN => Inf / Inf; sub to_str { my $v = shift; # native return 'Inf' if $v eq "".&Perl6::Value::Num::Inf; return '-Inf' if $v eq "".(-&Perl6::Value::Num::Inf); return 'NaN' if $v eq "".&Perl6::Value::Num::NaN; $v = 0 + $v; return 'Inf' if $v == Inf; return '-Inf' if $v == -&Inf; return 'NaN' if $v =~ m/n/i; return "" . $v } sub to_bit { $_[0] == 0 ? '' : 1 } sub to_num { 0 + $_[0] } sub to_int { int( $_[0] ) } package Perl6::Value::Int; sub to_str { no warnings 'uninitialized'; my $v = 0 + $_[0]; return 'Inf' if $v == Perl6::Value::Num::Inf; return '-Inf' if $v == -&Perl6::Value::Num::Inf; return 'NaN' if $v =~ m/n/i; return "" . $v } sub to_bit { $_[0] == 0 ? '' : 1 } sub to_num { 0 + $_[0] } sub to_int { $_[0] } package Perl6::Value::Str; sub to_str { $_[0] } sub to_bit { return '' if $_[0] eq '0' || $_[0] eq ''; return 1; } sub to_num { my $v = $_[0]; $v =~ s/\s+//g; # Perl 6 return Perl6::Value::Num::Inf if $v eq 'Inf'; return -&Perl6::Value::Num::Inf if $v eq '-Inf'; return Perl6::Value::Num::NaN if $v eq 'NaN'; # native return Perl6::Value::Num::Inf if $v eq "".&Perl6::Value::Num::Inf; return -&Perl6::Value::Num::Inf if $v eq "".(-&Perl6::Value::Num::Inf); return Perl6::Value::Num::NaN if $v eq "".&Perl6::Value::Num::NaN; no warnings 'numeric'; return 0 + $v; } sub to_int { Perl6::Value::Num::to_int( to_num( $_[0] ) ) } sub decrement { return '' if $_[0] eq '' || $_[0] eq 'a' || $_[0] eq '0'; my ($s,$c) = $_[0] =~ /(.*)(.)/; return str_decr($s) . 'z' if $c eq 'a'; return str_decr($s) . '9' if $c eq '0'; return $s . chr( ord($c) - 1 ); } package Perl6::Value::Bit; # built-in, unboxed 'bit' enums sub bool::false { '' } sub bool::true { 1 } sub taint::untainted { '' } sub taint::tainted { 1 } sub to_str { $_[0] ? 1 : '' } sub to_bit { $_[0] } sub to_num { $_[0] == 0 ? 0 : 1 } sub to_int { to_num( $_[0] ) } 1; __END__ =head1 NAME Perl6::Value - Perl6 boxed and unboxed Values =head1 SYNOPSIS use Perl6::Value; # unboxed Perl5 value my $num = Perl6::Value::Str::to_num( 'NaN' ); # Perl6 "Num" object my $num = Num->NaN; =head1 DESCRIPTION This module implements the "Value" classes - Num, Int, Str, Bit, Pair. It also implements platform-specific, low-level functions for "unboxed" Values. =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