292 lines
7.1 KiB
Perl
292 lines
7.1 KiB
Perl
|
|
|
|
###
|
|
### Value.pm
|
|
###
|
|
|
|
## Chapter 9 section 4.2
|
|
|
|
package Value;
|
|
|
|
my %op = ("add" =>
|
|
{
|
|
"FEATURE,FEATURE" => 'add_features',
|
|
"FEATURE,CONSTANT" => 'add_feature_con',
|
|
"FEATURE,TUPLE" => 'add_feature_tuple',
|
|
"TUPLE,TUPLE" => 'add_tuples',
|
|
"TUPLE,CONSTANT" => undef,
|
|
"CONSTANT,CONSTANT" => 'add_constants',
|
|
NAME => "Addition",
|
|
},
|
|
"mul" =>
|
|
{
|
|
NAME => "Multiplication",
|
|
"FEATURE,CONSTANT" => 'mul_feature_con',
|
|
"TUPLE,CONSTANT" => 'mul_tuple_con',
|
|
"CONSTANT,CONSTANT" => 'mul_constants',
|
|
},
|
|
);
|
|
sub op {
|
|
my ($self, $op, $operand) = @_;
|
|
my ($k1, $k2) = ($self->kindof, $operand->kindof);
|
|
my $method;
|
|
if ($method = $op{$op}{"$k1,$k2"}) {
|
|
$self->$method($operand);
|
|
} elsif ($method = $op{$op}{"$k2,$k1"}) {
|
|
$operand->$method($self);
|
|
} else {
|
|
my $name = $op{$op}{NAME} || "'$op'";
|
|
die "$name of '$k1' and '$k2' not defined";
|
|
}
|
|
}
|
|
sub negate { $_[0]->scale(-1) }
|
|
sub reciprocal { die "Nonlinear division" }
|
|
package Value::Constant;
|
|
@Value::Constant::ISA = 'Value';
|
|
|
|
sub new {
|
|
my ($base, $con) = @_;
|
|
my $class = ref $base || $base;
|
|
bless { WHAT => $base->kindof,
|
|
VALUE => $con,
|
|
} => $class;
|
|
}
|
|
|
|
sub kindof { "CONSTANT" }
|
|
|
|
sub value { $_[0]{VALUE} }
|
|
sub scale {
|
|
my ($self, $coeff) = @_;
|
|
$self->new($coeff * $self->value);
|
|
}
|
|
sub reciprocal {
|
|
my ($self, $coeff) = @_;
|
|
my $v = $self->value;
|
|
if ($v == 0) {
|
|
die "Division by zero";
|
|
}
|
|
$self->new(1/$v);
|
|
}
|
|
sub add_constants {
|
|
my ($c1, $c2) = @_;
|
|
$c1->new($c1->value + $c2->value);
|
|
}
|
|
|
|
sub mul_constants {
|
|
my ($c1, $c2) = @_;
|
|
$c1->new($c1->value * $c2->value);
|
|
}
|
|
package Value::Tuple;
|
|
@Value::Tuple::ISA = 'Value';
|
|
|
|
sub kindof { "TUPLE" }
|
|
|
|
sub new {
|
|
my ($base, %tuple) = @_;
|
|
my $class = ref $base || $base;
|
|
bless { WHAT => $base->kindof,
|
|
TUPLE => \%tuple,
|
|
} => $class;
|
|
}
|
|
sub components { keys %{$_[0]{TUPLE}} }
|
|
sub has_component { exists $_[0]{TUPLE}{$_[1]} }
|
|
sub component { $_[0]{TUPLE}{$_[1]} }
|
|
sub to_hash { $_[0]{TUPLE} }
|
|
sub scale {
|
|
my ($self, $coeff) = @_;
|
|
my %new_tuple;
|
|
for my $k ($self->components) {
|
|
$new_tuple{$k} = $self->component($k)->scale($coeff);
|
|
}
|
|
$self->new(%new_tuple);
|
|
}
|
|
sub has_same_components_as {
|
|
my ($t1, $t2) = @_;
|
|
my %t1c;
|
|
for my $c ($t1->components) {
|
|
return unless $t2->has_component($c);
|
|
$t1c{$c} = 1;
|
|
}
|
|
for my $c ($t2->components) {
|
|
return unless $t1c{$c};
|
|
}
|
|
return 1;
|
|
}
|
|
sub add_tuples {
|
|
my ($t1, $t2) = @_;
|
|
croak("Nonconformable tuples") unless $t1->has_same_components_as($t2);
|
|
|
|
my %result ;
|
|
for my $c ($t1->components) {
|
|
$result{$c} = $t1->component($c) + $t2->component($c);
|
|
}
|
|
$t1->new(%result);
|
|
}
|
|
sub mul_tuple_con {
|
|
my ($t, $c) = @_;
|
|
|
|
$t->scale($c->value);
|
|
}
|
|
package Intrinsic_Constraint_Set;
|
|
|
|
sub new {
|
|
my ($base, @constraints) = @_;
|
|
my $class = ref $base || $base;
|
|
bless \@constraints => $class;
|
|
}
|
|
|
|
sub constraints { @{$_[0]} }
|
|
sub apply {
|
|
my ($self, $func) = @_;
|
|
my @c = map $func->($_), $self->constraints;
|
|
$self->new(@c);
|
|
}
|
|
sub qualify {
|
|
my ($self, $prefix) = @_;
|
|
$self->apply(sub { $_[0]->qualify($prefix) });
|
|
}
|
|
sub union {
|
|
my ($self, @more) = @_;
|
|
$self->new($self->constraints, map {$_->constraints} @more);
|
|
}
|
|
package Synthetic_Constraint_Set;
|
|
|
|
sub new {
|
|
my $base = shift;
|
|
my $class = ref $base || $base;
|
|
|
|
my $constraints;
|
|
if (@_ == 1) {
|
|
$constraints = shift;
|
|
} elsif (@_ % 2 == 0) {
|
|
my %constraints = @_;
|
|
$constraints = \%constraints;
|
|
} else {
|
|
my $n = @_;
|
|
require Carp;
|
|
Carp::croak("$n arguments to Synthetic_Constraint_Set::new");
|
|
}
|
|
|
|
bless $constraints => $class;
|
|
}
|
|
sub constraints { values %{$_[0]} }
|
|
sub constraint { $_[0]->{$_[1]} }
|
|
sub labels { keys %{$_[0]} }
|
|
sub has_label { exists $_[0]->{$_[1]} }
|
|
sub add_labeled_constraint {
|
|
my ($self, $label, $constraint) = @_;
|
|
$self->{$label} = $constraint;
|
|
}
|
|
sub apply {
|
|
my ($self, $func) = @_;
|
|
my %result;
|
|
for my $k ($self->labels) {
|
|
$result{$k} = $func->($self->constraint($k));
|
|
}
|
|
$self->new(\%result);
|
|
}
|
|
sub qualify {
|
|
my ($self, $prefix) = @_;
|
|
$self->apply(sub { $_[0]->qualify($prefix) });
|
|
}
|
|
sub scale {
|
|
my ($self, $coeff) = @_;
|
|
$self->apply(sub { $_[0]->scale_equation($coeff) });
|
|
}
|
|
sub apply2 {
|
|
my ($self, $arg, $func) = @_;
|
|
my %result;
|
|
for my $k ($self->labels) {
|
|
next unless $arg->has_label($k);
|
|
$result{$k} = $func->($self->constraint($k),
|
|
$arg->constraint($k));
|
|
}
|
|
$self->new(\%result);
|
|
}
|
|
|
|
|
|
## Chapter 9 section 4.2.5
|
|
|
|
sub apply_hash {
|
|
my ($self, $hash, $func) = @_;
|
|
my %result;
|
|
for my $c (keys %$hash) {
|
|
my $dotc = ".$c";
|
|
for my $k ($self->labels) {
|
|
next unless $k eq $c || substr($k, -length($dotc)) eq $dotc;
|
|
$result{$k} = $func->($self->constraint($k), $hash->{$c});
|
|
}
|
|
}
|
|
$self->new(\%result);
|
|
}
|
|
package Value::Feature;
|
|
@Value::Feature::ISA = 'Value';
|
|
|
|
sub kindof { "FEATURE" }
|
|
|
|
sub new {
|
|
my ($base, $intrinsic, $synthetic) = @_;
|
|
my $class = ref $base || $base;
|
|
my $self = {WHAT => $base->kindof,
|
|
SYNTHETIC => $synthetic,
|
|
INTRINSIC => $intrinsic,
|
|
};
|
|
bless $self => $class;
|
|
}
|
|
sub new_from_var {
|
|
my ($base, $name, $type) = @_;
|
|
my $class = ref $base || $base;
|
|
$base->new($type->qualified_intrinsic_constraints($name),
|
|
$type->qualified_synthetic_constraints($name),
|
|
);
|
|
}
|
|
sub intrinsic { $_[0]->{INTRINSIC} }
|
|
sub synthetic { $_[0]->{SYNTHETIC} }
|
|
sub scale {
|
|
my ($self, $coeff) = @_;
|
|
return
|
|
$self->new($self->intrinsic,
|
|
$self->synthetic->scale($coeff),
|
|
);
|
|
}
|
|
sub add_features {
|
|
my ($o1, $o2) = @_;
|
|
my $intrinsic = $o1->intrinsic->union($o2->intrinsic);
|
|
my $synthetic = $o1->synthetic->apply2($o2->synthetic,
|
|
sub { $_[0]->add_equations($_[1]) },
|
|
);
|
|
$o1->new($intrinsic, $synthetic);
|
|
}
|
|
sub mul_feature_con {
|
|
my ($o, $c) = @_;
|
|
$o->scale($c->value);
|
|
}
|
|
sub add_feature_con {
|
|
my ($o, $c) = @_;
|
|
my $v = $c->value;
|
|
my $synthetic = $o->synthetic->apply(sub { $_[0]->add_constant($v) });
|
|
$o->new($o->intrinsic, $synthetic);
|
|
}
|
|
sub add_feature_tuple {
|
|
my ($o, $t) = @_;
|
|
my $synthetic =
|
|
$o->synthetic->apply_hash($t->to_hash,
|
|
sub {
|
|
my ($constr, $comp) = @_;
|
|
my $kind = $comp->kindof;
|
|
if ($kind eq "CONSTANT") {
|
|
$constr->add_constant($comp->value);
|
|
} elsif ($kind eq "FEATURE") {
|
|
$constr->add_equations($comp->synthetic->constraint(""));
|
|
} elsif ($kind eq "TUPLE") {
|
|
die "Tuple with subtuple component";
|
|
} else {
|
|
die "Unknown tuple component type '$kind'";
|
|
}
|
|
},
|
|
);
|
|
$o->new($o->intrinsic, $synthetic);
|
|
}
|
|
|
|
1;
|