typesafety

 view release on metacpan or  search on metacpan

typesafety.pm  view on Meta::CPAN

#    or possible start to most deeply nested ops and work outwards, but we might miss prototypes etc.
#    move to tracking return values to and from each seq() number?
#    or, if we're fully recursive, we could walk the op tree ourselves, just going sibling to sibling
#    at the top level, recursing into the depths. yeah, i like that. okey, that's what we've done.
# v/ $a->meth() - $methods{$scalars->{targ of $a}->type()}->{'meth'} should exist, period, or we're trying to
#    call an unprototyped method.
# v/ when multiple things are declared on one line, they each still get their own nextstate.
#    this means we need only process them in the same order. each could link to the next.
#    was being silly about this. now the "declare" subs are just stubs and we extract original
#    data from the bytecode tree.
# v/ drop attributes, perhaps, and use proto() for scalars too. prototype proto():
#    declare FooBar => my $a; 
#    didn't drop attributes, but we added declare().
# v/ use ->sibling() rather than ->next() to skip over sub-expressions when parsing B - in SOME places
# v/ declare() needs B parser logic to back it up
# v/ beware looking for nextstate! makes compound instructions impossible!
# v/ inspect methods in all modules, not just root level main
# v/ CHECK { } is too soon - inserting code into the main tree at CHECK time might be the best solution.
#    scalar attributes and proto() are done runtime, so our check routine would have to be triggered
#    from the end?! just before the main loop? hrm. don't know how this is going to work. manual call
#    for now.

use 5.8.8;
use strict;
no strict 'refs';
use warnings;
our $VERSION = '0.05';

use B;
use B::Generate;

#
# constants
#

use B qw< ppname >;
use B qw< OPf_KIDS OPf_STACKED OPf_WANT OPf_WANT_VOID OPf_WANT_SCALAR OPf_WANT_LIST >;
use B qw< OPpTARGET_MY >;
use B qw< SVf_IOK SVf_NOK SVf_POK SVf_IVisUV >;
sub SVs_PADMY () { 0x00000400 }     # use B qw< SVs_PADMY >;

#
# variables
#

my %knownuniverse; # all known namespaces - our inspection list
my %args;          # use-time arguments
my $debug = 0;     # debug mode flag
our $curcv;        # codevalue to get pad entries from
our $returnvalue;  # typeob expected of returns, if any

my $lastline; my $lastfile; my $lastpack;  # set from COP statements as we go 

#
# debugging
#

use Carp 'confess', 'cluck';
use B::Concise 'concise_cv';

sub debug { my @args = @_; my $line = (caller)[2]; print "debug: $line: ", @args, "\n" if $debug; }

sub nastily () { " in package $lastpack, file $lastfile, line $lastline"; }

# $SIG{__DIE__} =  $SIG{INT} = sub {
#    # when someone does kill -INT <our pid> from the command line, dump our stack and exit
#    print STDERR shift, map { (caller($_))[0] ? sprintf("%s at line %d\n", (caller($_))[1,2]) : ''; } 0..30;
#    print STDERR join "\n", @_;
#    exit 1;
# };

#
# allow user to specify what types ought to be
#

sub import {
  my $caller = caller;
  $args{$_}++ foreach @_;
  $debug = $args{debug};
  push @{$caller.'::ISA'}, 'typesafety'; 
  $knownuniverse{$caller}++; 
  *{$caller.'::proto'} = sub {
     my $method = shift;
     die qq{proto syntax: proto 'foo', returns => 'FooBar', takes => 'FooBar', undef, 'FooBar', 'BazQux', undef, undef, undef;\n}
       unless 'returns' eq shift; 
     my $type = shift;
     my $takes = [];
     if(@_) {
       die unless 'takes' eq shift; 
       $takes = [ @_ ];
     }
     (undef, my $filename, my $line) = caller(0); # XX nasty hardcode
     die "filename" unless $filename; die "line" unless $line;
     my $typeobject = typesafety::methodob->new(
          type=>$type, package=>$caller, filename=>$filename, line=>$line, 
          name=>$method, desc=>'prototyped method', takes=>$takes,
     );
     $typeobject->literal = $type if $method eq 'new'; # shift_prototype() for new() returns identity. bless looks for lit in this.
     define_method($caller, $method, $typeobject);
  };
  *{$caller.'::declare'} = sub :lvalue { 
    $_[1] 
  };
  return 1;
}

#
# data structures
#

{

  my $methods;       # $methods->{$package}->{$methodname} = typeob
  my $scalars;       # $scalars->{$curcv}->{$targ} = typeob

  sub lookup_method {
    my $package = shift;
    my $method = shift;
    return exists $methods->{$package} if ! defined $method;
    return $methods->{$package}->{$method} if defined $package and defined $method and exists $methods->{$package} and exists $methods->{$package}->{$method};
    return undef;

typesafety.pm  view on Meta::CPAN

     $op->can('targ') and 
     # ppname($op->targ()) eq 'pp_list' and
     $op->flags() & OPf_KIDS and
     $op->can('first')
   ) {
     debug('denull descending past null op ', ppname($op->targ()), ' to: ', $op->first()->name());
     $op = $op->first();
     return denull($op);
   }

   return $op;

}

sub source_status { return ($lastpack, $lastfile, $lastline) }


#
# typeob
#

# represents a type itself, including where it was defined, how it was defined, and how it is actually used.
# type data associated with a typed scalar - includes verbose information about where the type was defined
# for good diagnostics in case of error

package typesafety::typeob;

# accessors

sub type     :lvalue { $_[0]->[0] }          # point of our existance
sub package  :lvalue { $_[0]->[1] }          # diagnostic output in case of type match failure
sub filename :lvalue { $_[0]->[2] }          # diagnostics
sub line     :lvalue { $_[0]->[3] }          # diagnostics
sub pad      :lvalue { $_[0]->[4] }          # scalars only 
sub name     :lvalue { $_[0]->[5] }          # scalars only 
sub desc     :lvalue { $_[0]->[6] }          # scalar or method return? diagnostics? creating info?
sub takes    :lvalue { $_[0]->[7] }          # in the case of methods, what types (in order) do we take for args?

sub accepts  { [] }                          # 8 - typesafety::arrayob does something with this
sub emits    { [] }                          # 9 - noop in this base class
sub accept   { }                             # noop in this base class
sub emit     { }                             # noop in this bsae class

sub literal  :lvalue { $_[0]->[10] }         # literal value stored in scalar, if known. new's prototype is reused as prototype first arg to new.
sub created  :lvalue { $_[0]->[11] }         # internal debugging - which program line called the constructor


sub argnum     { typesafety::confess }       # 12 - next arg to be read, methodobs
sub subobjects { typesafety::confess }       # 12 - hashes contain other invidually typed objects

sub reset_prototype  { typesafety::confess } # typesafety::methodob 
sub shift_prototype  { typesafety::confess } # typesafety::methodob 
sub aelem_prototype  { typesafety::confess } # typesafety::methodob 

sub new { 
  my $self = bless ['none', typesafety::source_status(), (undef) x 4, [], [], undef], shift(); 
  while(@_) { 
    my $f = shift; $self->$f = shift; 
  } 
  # ignore the output string, just make sure that the fields required for diagnostics are defined
  $self->created = (caller)[2]; 
  $self->diagnostics(); 
  typesafety::debug("typesafety::typeob: new: created ", $self->diagnostics());
  return $self; 
}

sub clone { 
  my $self = shift; 
  my @new = @$self; 
  # augment description - this is the primary (only?) change made to clones
  $new[6] ||= ''; $new[6] .= ' ' . $_[0] if @_;  
  typesafety::debug("new desc is $new[6]");
  # typesafety::cluck("new desc");
  bless \@new, ref $self;
}

sub diagnostics { 
  my $self = shift;
  my @diag;
  push @diag, $self->desc if $self->desc;
  push @diag, $self->name if $self->name; # typesafety::confess unless $self->name;
  push @diag, 'type ' . $self->type if $self->type; # typesafety::confess unless $self->type;
  push @diag, 'containing the literal value "' . $self->literal . '"' if defined $self->literal;
Carp::confess "strange crap in 'takes'" if $self->takes and ref($self->takes) ne 'ARRAY';
  push @diag, 'taking as arguments the types "' . join(', ', map $_||'(undef)', @{ $self->takes }) . '"' if $self->takes and ref($self->takes) eq 'ARRAY';
  push @diag, 'created inside typesafety by request from line ' . $self->created if $debug;
  push @diag, sprintf 'defined in package %s, file %s, line %s', $self->package, $self->filename, $self->line if $self->package or $self->filename;
  return join ', ', @diag;
}

sub isa {
  # wrapper for the normal UNIVERSAL::isa() test - should two typeobs not match, we die with diagnostics
  my $self = shift; 
  my $arewe = shift or typesafety::confess(); 
  ref $arewe or typesafety::confess(); 
  my $fatal = shift; 
  return 1 if ! $self->type();  # despite having a type object, they're untyped... this happens with methods prototyped to (undef; ...)
  return 1 if ! $arewe->type();  # despite having a type object, we're untyped... this happens with methods prototyped to (undef; ...)
  return 1 if $self->type() eq $arewe->type() or $self->type()->isa($arewe->type()); 
  return undef unless $fatal;
  die join ' ', $fatal, ($fatal?': ':''),  "type mismatch: expected: ", $arewe->diagnostics(), 
                "; got: ", $self->diagnostics(), typesafety::nastily(); 
}

package typesafety::scalarob; 

use base 'typesafety::typeob'; 

sub typesafety::scalarob::stuff { 'scalar' }

#
# typesafety::methodob
#

package typesafety::methodob; 

use base 'typesafety::typeob'; 

sub typesafety::methodob::stuff { 'method' }

sub argnum    :lvalue { $_[0]->[12] }   # next argument position to be read 



( run in 2.271 seconds using v1.01-cache-2.11-cpan-b16cb0d3907 )