#!/usr/bin/perl package Blondie::TypeSafe; use strict; use warnings; use Blondie::ADTs; use Blondie::Nodes; sub new { my $class = shift; bless { }, $class; } sub typecheck { my $self = shift; my $runtime = shift; my $prog = shift; Blondie::TypeSafe::Flatten->new->reduce( Blondie::TypeSafe::Annotator->new->annotate($runtime, $prog) ); } { package Blondie::TypeSafe::Annotation; use base qw/Blondie::Node::Map/; use Carp qw/croak/; sub fmap { my $self = shift; die "annotations are not fmappable, flatten first"; } sub values { my $self = shift; croak "annotations are not eatable, flatten first"; } sub type { my $self = shift; my $type = $self->SUPER::type; # this method always returns the shallowest possible scalar value # simplify [["Foo"]] into ["Foo"] #$type = [$type]; # and "Foo" into ["Foo"] while (ref $type and ref $type eq 'ARRAY' and @$type == 1){ $type = $type->[1]; } return $type; } package Blondie::TypeSafe::Type; use base qw/Blondie::Map/; package Blondie::TypeSafe::Type::Bottom; use base qw/Blondie::TypeSafe::Type/; sub type { $_[0] } package Blondie::TypeSafe::Type::Placeholder; use base qw/Blondie::TypeSafe::Type/; sub set { my $self = shift; my $type = shift; warn Carp::longmess unless ref $type; $self->{type} = $type->type; bless $self, "Blondie::TypeSafe::Type::Std"; } package Blondie::TypeSafe::Type::Std; use base qw/Blondie::TypeSafe::Type/; sub new { my $class = shift; my $type = shift; $class->SUPER::new(type => $type); } } { package Blondie::TypeSafe::Flatten; use base qw/Blondie::Reducer/; use Scalar::Util (); sub new { bless {}, shift } sub reduce { my $self = shift; my $node = shift; my $h = Devel::STDERR::Indent::indent(); warn "flattenning $node " . (eval { $node->struct_equiv } || "(not an object)"); Scalar::Util::blessed($node) ? $self->SUPER::reduce($node->struct_equiv) : $node; } } { package Blondie::TypeSafe::Annotator; use base qw/Blondie::Reducer Blondie::Reducer::DynamicScoping Class::Accessor/; use Scalar::Util (); use B (); use B::Flags (); use String::Escape qw/qprintable/; use Devel::STDERR::Indent qw/indent $STRING/; $STRING = " "; BEGIN { __PACKAGE__->mk_accessors(qw/runtime/) } sub annotate { my $self = shift; my $runtime = shift; my $prog = shift; $self->{runtime} = $runtime; $self->reduce($prog); } sub reduce { my $self = shift; my $h = indent; my $node = shift; warn "reducing " . qprintable($node) . "..."; my $res = $self->SUPER::reduce($node); warn "reduce(" . (ref($node) ? $node : qprintable($node)) .") = $res (type = " . $self->display_type($res->type) . ")"; $res; } sub new { my $class = shift; bless {}, $class; } sub generic_reduce { my $self = shift; my $node = shift; if (Scalar::Util::blessed($node) and $node->can("type")){ return Blondie::TypeSafe::Annotation->new( type => $node->type, struct_equiv => $node, ); } else { my $type = $self->guess_perl_type($node); warn "type of " . qprintable($node) . " is $type"; return Blondie::TypeSafe::Annotation->new( type => Blondie::TypeSafe::Type::Std->new($type), struct_equiv => $node, orig => $node, ); } } sub guess_perl_type { my $self = shift; my $node = shift; my $obj = B::svref_2object(ref($node) ? $node : \$node); my $class = B::class($obj); return $class unless $class eq "PVMG"; my $flags = $obj->flagspv; return "IV" if $flags =~ "IOK"; return "PV" if $flags =~ "POK"; die "what type is $node ($flags)?"; } sub reduce_sym { my $self = shift; my $sym = shift; my $type = $self->find_dyn_sym($sym->val)->val; warn "linking the symbol " . $sym->val . " to $type"; Blondie::TypeSafe::Annotation->new( type => $type, struct_equiv => $sym->fmap(sub { Blondie::TypeSafe::Annotation->new(struct_equiv => $_[0]) }), ); } sub reduce_val { my $self = shift; my $val = shift; my $rval = $val->fmap(sub { $self->reduce($_[0]) }); my $sub = $rval->val; Blondie::TypeSafe::Annotation->new( type => $sub->type, struct_equiv => $rval, orig => $val, ); } sub reduce_app { my $self = shift; my $app = shift; my $rapp = $app->fmap(sub { $self->reduce($_[0]) }); my ($thunk_val, @params) = $rapp->values; my $thunk_type = $thunk_val->type; my @context = ref $thunk_type eq "ARRAY" ? @$thunk_type : $thunk_type; my $return_type = pop @context; warn "Unifying paramter types with thunk prototype in $app"; my $i; my $unified_app = $rapp->fmap(sub { my $node = shift; return $node unless $i++; # skip the thunk my $expected = shift @context; #warn "## unifying " . $self->display_type($expected) . " with " . Data::Dumper::Dumper($node); $self->unify_type($node, $expected); }); return Blondie::TypeSafe::Annotation->new( type => $return_type, orig => $app, struct_equiv => $unified_app, ); } sub simplify_type { my $self = shift; my $type = shift; while (Scalar::Util::blessed($type) and !$type->isa("Blondie::TypeSafe::Type::Placeholder")){ my $new = $type->type; return $type if ref $new and $new == $type; $type = $new; } $type; } sub display_type { my $self = shift; my $type = $self->simplify_type(@_); Scalar::Util::blessed($type) ? '' : (ref $type ? ("( " . join(" -> ", map { $self->display_type($_) } @{$type}) . " )") : $type); } sub unify_type { my $self = shift; my $node = shift; my $expected_type = shift; my $de_facto_type = $node->type; warn "attempting to unify " . $node->struct_equiv . " whose type is " . $self->display_type($de_facto_type) . " with " . $self->display_type($expected_type); warn Data::Dumper::Dumper($expected_type) unless defined $self->simplify_type($expected_type); return $node if $self->types_eq($de_facto_type, $expected_type); warn "types do not eq"; if ($self->is_placeholder($de_facto_type)) { warn "filling " . $self->display_type($expected_type) . " for placeholder of " . Blondie::Emitter::Pretty->new->string(Blondie::TypeSafe::Flatten->new->reduce($node)); $de_facto_type->set($expected_type); return $node; } else { warn "casting"; return $self->runtime->cast_node_type($node, $de_facto_type, $expected_type); } } sub is_placeholder { my $self = shift; my $type = $self->simplify_type(shift); Scalar::Util::blessed($type) and $type->isa("Blondie::TypeSafe::Type::Placeholder") } sub types_eq { my $self = shift; my $h = indent; my $x = shift; my $y = shift; warn "comparing $x and $y"; if (Scalar::Util::blessed($x) and Scalar::Util::blessed($y)) { die "baaah! expected type cannot be placeholder" if $y->isa("Blondie::TypeSafe::Type::Placeholder"); if (!$x->isa("Blondie::TypeSafe::Type::Placeholder")) { return $self->types_eq($x->type, $y->type); } else { return undef; } } elsif (ref($x) and ref($y)) { return unless @$x == @$y; my @x = @$x; my @y = @$y; while (@x) { return unless $self->types_eq(pop @x, pop @y) } return 1; } elsif (!ref($x) and !ref($y)) { return $x eq $y; } else { return; } } sub reduce_thunk { my $self = shift; my $thunk = shift; $self->enter_scope; my $rthunk = $thunk->fmap(sub { $self->reduce($_[0]) }); $self->leave_scope; my $child = $rthunk->val->struct_equiv; if ($child->isa("Blondie::Seq")){ my @children_of_seq = $child->values; # the children of the seq child of the thunk my $last = pop @children_of_seq; my @params = grep { $_->struct_equiv->isa("Blondie::Param") } @children_of_seq; return Blondie::TypeSafe::Annotation->new( type => [ ( map { $_->accepts_type } @params ) => $last->type, ], struct_equiv => $rthunk, orig => $thunk, ); } else { return Blondie::TypeSafe::Annotation->new( type => [ $child->type ], struct_equiv => $rthunk, orig => $thunk, ); } } sub reduce_seq { my $self = shift; my $seq = shift; my $rseq = $seq->fmap(sub { $self->reduce($_[0]) }); return Blondie::TypeSafe::Annotation->new( type => ($rseq->values)[-1]->type, struct_equiv => $rseq, orig => $seq, ); } sub reduce_param { my $self = shift; my $param = shift; $self->new_pad( $param->val, my $placeholder = Blondie::TypeSafe::Type::Placeholder->new ); my $rparam = $param->fmap(sub { Blondie::TypeSafe::Annotation->new( struct_equiv => $_[0], type => Blondie::TypeSafe::Type::Bottom->new, ); }); return Blondie::TypeSafe::Annotation->new( type => Blondie::TypeSafe::Type::Bottom->new, accepts_type => $placeholder, struct_equiv => $rparam, orig => $param, ); } sub can_reduce { my $self = shift; my $node = shift; 1; } } __PACKAGE__; __END__ =pod =head1 NAME Blondie::TypeSafe - =head1 SYNOPSIS use Blondie::TypeSafe; =head1 DESCRIPTION =cut