Anarres-Mud-Driver

 view release on metacpan or  search on metacpan

lib/Driver/Compiler/Check.pm  view on Meta::CPAN

package Anarres::Mud::Driver::Compiler::Check;

use strict;
use vars qw(@ISA @EXPORT_OK @STACK $DEBUG
		%OPTYPETABLE %OPTYPES %OPCHOICES);
use Carp qw(:DEFAULT cluck);
use Data::Dumper;
use List::Util qw(first);
use Anarres::Mud::Driver::Compiler::Type qw(:all);
use Anarres::Mud::Driver::Compiler::Node qw(:all);

# This has turned into a rather long, complex and involved Perl file.

# Error messages starting with [D] are duplicating work done elsewhere
# and are candidates for removal.

push(@Anarres::Mud::Driver::Compiler::Node::ISA, __PACKAGE__);

sub DBG_TC_NAME		() { 1 }
sub DBG_TC_PROMOTE	() { 2 }
sub DBG_TC_CONVERT	() { 4 }

$DEBUG = 0;;
$DEBUG |= DBG_TC_NAME		if 0;
$DEBUG |= DBG_TC_PROMOTE	if 0;
$DEBUG |= DBG_TC_CONVERT	if 0;

@STACK = ();

sub debug_tc {
	my ($self, $class, @args) = @_;
	return undef unless $DEBUG & $class;
	my $msg = join(": ", @args);
	print STDERR "DebugTC: $msg\n";
}

# Called at the beginning of any typecheck call
sub tc_start {
	my ($self, @args) = @_;
	push(@STACK, $self);
	$self->debug_tc(DBG_TC_NAME, "Checking " . $self->opcode, @args);
}

# Called at the end of any typecheck call, possibly by tc_fail().
sub tc_end {
	my ($self, $type, @args) = @_;
	$self->settype($type) if $type;
	$self->debug_tc(DBG_TC_NAME, "Finished " . $self->opcode, @args);
	pop(@STACK);
	return 1;	# Make it return a success.
}

	# This is a utility method. Calling it is mandatory
	# in the case of failure.
sub tc_fail {
	my ($self, $type, @args) = @_;
	$type = T_FAILED unless $type;
	$self->tc_end($type, @args);
	return undef;	# Make it return a failure.
}




sub LV ($) { return [ $_[0], F_LVALUE ] }

# Opcodes which are choice targets and provide a custom convert
# are marked up as 'NOCHECK'.

%OPTYPES = (
	StmtNull	=> [									T_VOID ],
	ExpComma	=> 'CODE',

		(map { $_ => 'NOCHECK' } qw(
			IntAssert StrAssert ArrAssert MapAssert ClsAssert ObjAssert
			ToString
				)),

	# It's faster to give these two custom code as well.
	# Nil			=> [								T_NIL ],
	# String		=> [								T_STRING ],
		(map { $_ => 'CODE' } qw(
			Nil String Integer Array Mapping Closure Variable Parameter
			Funcall CallOther
				)),
		(map { $_ => 'NOCHECK' } qw(
			VarStatic VarGlobal VarLocal
				)),

	Unot		=> [ T_UNKNOWN,							T_BOOL ],
	Tilde		=> [ T_INTEGER,							T_INTEGER ],
	Plus		=> [ T_INTEGER,							T_INTEGER ],
	Minus		=> [ T_INTEGER,							T_INTEGER ],

	Postinc		=> [ LV(T_INTEGER),						T_INTEGER ],
	Postdec		=> [ LV(T_INTEGER),						T_INTEGER ],
	Preinc		=> [ LV(T_INTEGER),						T_INTEGER ],
	Predec		=> [ LV(T_INTEGER),						T_INTEGER ],
		(map { $_ => 'CHOOSE' } qw(
			Eq Ne Lt Gt Le Ge

			Add Sub Mul Div Mod
			Or And Xor
			Lsh Rsh

			AddEq SubEq DivEq MulEq ModEq
			AndEq OrEq XorEq
			LshEq RshEq

lib/Driver/Compiler/Check.pm  view on Meta::CPAN

#					or die "No 'convert' in package $package\::$tp$op";


# A lot of superclass methods. These are found in ::Check via @ISA.

sub lvaluep { undef; }
sub constp { undef; }

sub assert {	# This sucks somewhat
	my ($self, $type) = @_;
	if (!$self->type->equals(T_UNKNOWN)) {	# DEBUGGING
		confess "Asserting something of known type.";
	}
	print "Asserting " . $self->opcode . " into " . ${$type} . "\n";
	return new Anarres::Mud::Driver::Compiler::Node::IntAssert($self)
					if $type->equals(T_INTEGER);
	return new Anarres::Mud::Driver::Compiler::Node::StrAssert($self)
					if $type->equals(T_STRING);
	return new Anarres::Mud::Driver::Compiler::Node::ArrAssert($self)
					if $type->is_array;
	return new Anarres::Mud::Driver::Compiler::Node::MapAssert($self)
					if $type->is_mapping;
	return new Anarres::Mud::Driver::Compiler::Node::ClsAssert($self)
					if $type->equals(T_CLOSURE);
	return new Anarres::Mud::Driver::Compiler::Node::ObjAssert($self)
					if $type->equals(T_OBJECT);
	confess "Cannot assert node into type " . $$type . "!\n";
	return undef;
}

sub promote_to_block {
	my ($self, $stmt) = @_;

	return $stmt if ref($stmt) =~ /::Block$/;
	confess "Can only promote statements into blocks, not " .
			$stmt->opcode
					unless ref($stmt) =~ /::Stmt[^:]+$/;

	# It's a statement. This code is partially duplicated below.
	return new Anarres::Mud::Driver::Compiler::Node::Block(
					[],	# locals
					[ $stmt ]);
}

sub idx_promote_to_block {
	my ($self, $index) = @_;
	my $stmt = $self->value($index);
	my $block = $self->promote_to_block($stmt);
	$self->setvalue($index, $block);
	return $block;
}

# There is a special case of this in Integer.
sub promote {
	my ($self, $newtype) = @_;
	my $type = $self->type;
	# XXX Checking for T_UNKNOWN is wrong here. I need to check
	# whether the old type is 'weaker' than the new type.
	confess "XXX No type in " . $self->dump unless $type;
	return $self if $type->equals($newtype);
	$self->debug_tc(DBG_TC_PROMOTE, "Promoting ([" . $type->dump . "] ".
					$self->opcode . ") into " . $newtype->dump);

	# Anything can become 'unknown' - this allows weakening
	return $self if $type->compatible($newtype);

	# This should really be done by 'compatible'?
	return $self if $newtype->equals(T_BOOL);

	# The Assert nodes are broken for some reason?
	# return $self->assert($newtype) if $type->equals(T_UNKNOWN);
	return $self if $type->equals(T_UNKNOWN);	# Should assert

	return $self
		if $type->equals(T_INTEGER) && $newtype->equals(T_STRING);
	# return $type->promote($self, $newtype);
	return undef;
}

# This might return an undef in the error list in the case that an
# error occurred which has already been reported.
sub convert {
	my ($self, $program, @rest) = @_;

	my $opcode = $self->opcode;

	$self->debug_tc(DBG_TC_CONVERT, "Convert " . $self->opcode .
					" to " . $opcode);

	unless (ref $OPTYPES{$opcode}) {
		confess "XXX OPTYPES for $opcode is $OPTYPES{$opcode}"
				if $OPTYPES{$opcode};
		confess "XXX No OPTYPES for $opcode!";
	}

	my @values = $self->values;
	my @template = @{ $OPTYPES{$opcode} };
	my $rettype = pop(@template);

	unless (@values == @template) {
		# XXX This is for self-debugging.
		print STDERR "I have " . scalar(@values) . " values\n";
		print STDERR "I have " . scalar(@template) . " template\n";
		die "Child count mismatch in $opcode";
	}

	# We push undef into @errors to indicate that an error occurred
	# but should have been reported already at a lower level.

	my $i = 0;
	my @tvals = ();
	my @errors = ();
	foreach my $type (@template) {
		my $val = $values[$i];
		my ($tval, @assertions);

		# XXX I should promote unknown to anything, not
		# assert directly in convert.

		if (ref($type) eq 'ARRAY') {
			@assertions = @$type;
			$type = shift @assertions;
		}

		if (!defined $type) {
			$tval = $val;
		}
		elsif ($type eq 'BLOCK') {
			$tval = $self->promote_to_block($val);
			$tval->check($program, @rest)
							or push(@errors, undef);
		}
		else {
			if (!$val->check($program, @rest)) {
				push(@errors, undef);
			}
			elsif (!($tval = $val->promote($type))) {
				push(@errors, "Cannot promote " . $val->opcode .
								" from " . $val->type->name .
								" to " . $type->name .
								" for argument $i of " . $self->opcode);
			}
		}

		# return undef unless $tval;

		# XXX Perform assertions.
		foreach (@assertions) {
			if ($_ == F_LVALUE) {
				unless ($tval->lvaluep) {
					push(@errors, $val->opcode . " is not an lvalue in "
									. $self->opcode);
				}
			}
			else {
				die "Unknown assertion $_!";
			}
		}

		push(@tvals, $tval);
	}



( run in 1.265 second using v1.01-cache-2.11-cpan-39bf76dae61 )