From: Mike G. v. a. <we...@ma...> - 2008-06-24 00:02:39
|
Log Message: ----------- Merging HEAD to rel-2-4-dev 6/23/2008 View Value.pm for a complete list of changes. Tags: ---- rel-2-4-dev Modified Files: -------------- pg/lib: AnswerHash.pm Fraction.pm List.pm Parser.pm Units.pm Value.pm VectorField.pm pg/lib/Parser: BOP.pm Complex.pm Constant.pm Context.pm Differentiation.pm Function.pm Item.pm List.pm Number.pm String.pm UOP.pm Value.pm Variable.pm pg/lib/Parser/BOP: equality.pm multiply.pm power.pm pg/lib/Parser/Context: Default.pm Functions.pm Parens.pm pg/lib/Parser/Function: complex.pm pg/lib/Parser/Legacy: LimitedComplex.pm LimitedNumeric.pm NumberWithUnits.pm Numeric.pm PGcomplexmacros.pl pg/lib/Parser/UOP: minus.pm pg/lib/Value: AnswerChecker.pm Complex.pm Context.pm Formula.pm Infinity.pm Interval.pm List.pm Matrix.pm Point.pm Real.pm Set.pm String.pm Union.pm Vector.pm pg/lib/Value/Context: Data.pm pg/lib/WeBWorK: EquationCache.pm pg/lib/WeBWorK/PG: Translator.pm Added Files: ----------- pg/lib: Applet.pm Revision Data ------------- Index: minus.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Parser/UOP/minus.pm,v retrieving revision 1.4.8.1 retrieving revision 1.4.8.2 diff -Llib/Parser/UOP/minus.pm -Llib/Parser/UOP/minus.pm -u -r1.4.8.1 -r1.4.8.2 --- lib/Parser/UOP/minus.pm +++ lib/Parser/UOP/minus.pm @@ -29,11 +29,13 @@ sub _reduce { my $self = shift; my $op = $self->{op}; my $reduce = $self->{equation}{context}{reduction}; - $self = $op->{op} if $op->isNeg && $reduce->{'-(-x)'}; + return $op->{op} if $op->isNeg && $reduce->{'-(-x)'}; + return $op if $op->{isZero} && $reduce->{'-0'}; return $self; } $Parser::reduce->{'-(-x)'} = 1; +$Parser::reduce->{'-0'} = 1; ######################################################################### Index: Real.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Real.pm,v retrieving revision 1.24.2.1 retrieving revision 1.24.2.2 diff -Llib/Value/Real.pm -Llib/Value/Real.pm -u -r1.24.2.1 -r1.24.2.2 --- lib/Value/Real.pm +++ lib/Value/Real.pm @@ -6,7 +6,7 @@ package Value::Real; my $pkg = 'Value::Real'; -use strict; +use strict; no strict "refs"; our @ISA = qw(Value); # @@ -17,7 +17,8 @@ my $self = shift; my $class = ref($self) || $self; my $context = (Value::isContext($_[0]) ? shift : $self->context); my $x = shift; $x = [$x,@_] if scalar(@_) > 0; - return $x if Value::isReal($x); + return $x->inContext($context) if Value::isReal($x); + $x = [$x] unless ref($x) eq 'ARRAY'; Value::Error("Can't convert ARRAY of length %d to %s",scalar(@{$x}),Value::showClass($self)) unless (scalar(@{$x}) == 1); @@ -72,41 +73,41 @@ # sub add { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); - return $self->make($l->{data}[0] + $r->{data}[0]); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + return $self->inherit($other)->make($l->{data}[0] + $r->{data}[0]); } sub sub { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); - return $self->make($l->{data}[0] - $r->{data}[0]); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + return $self->inherit($other)->make($l->{data}[0] - $r->{data}[0]); } sub mult { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); - return $self->make($l->{data}[0] * $r->{data}[0]); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + return $self->inherit($other)->make($l->{data}[0] * $r->{data}[0]); } sub div { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); Value::Error("Division by zero") if $r->{data}[0] == 0; - return $self->make($l->{data}[0] / $r->{data}[0]); + return $self->inherit($other)->make($l->{data}[0] / $r->{data}[0]); } sub power { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my $x = $l->{data}[0] ** $r->{data}[0]; - return $self->make($x) unless $x eq 'nan'; + return $self->inherit($other)->make($x) unless $x eq 'nan'; Value::Error("Can't raise a negative number to a power") if ($l->{data}[0] < 0); Value::Error("Result of exponention is not a number"); } sub modulo { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); $l = $l->{data}[0]; $r = $r->{data}[0]; - return $self->make(0) if $r == 0; # non-fuzzy check + return $self->inherit($other)->make(0) if $r == 0; # non-fuzzy check my $m = $l/$r; my $n = int($m); $n-- if $n > $m; # act as floor() rather than int() - return $self->make($l - $n*$r); + return $self->inherit($other)->make($l - $n*$r); } sub compare { @@ -116,6 +117,8 @@ # my $m = $self->getFlag("period"); if (defined $m) { + $l = $l->with(period=>undef); # make sure tests below don't use period + $r = $r->with(period=>undef); if ($self->getFlag("logPeriodic")) { return 1 if $l->value == 0 || $r->value == 0; # non-fuzzy checks $l = log($l); $r = log($r); @@ -161,8 +164,8 @@ sub cos {my $self = shift; $self->make(CORE::cos($self->{data}[0]))} sub atan2 { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); - return $self->make(CORE::atan2($l->{data}[0],$r->{data}[0])); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + return $self->inherit($other)->make(CORE::atan2($l->{data}[0],$r->{data}[0])); } ################################################## @@ -170,7 +173,8 @@ sub string { my $self = shift; my $equation = shift; my $prec = shift; my $n = $self->{data}[0]; - my $format = $self->getFlag("format",$equation->{format} || ($equation->{context} || $self->context)->{format}{number}); + my $format = $self->getFlag("format",$equation->{format} || + ($equation->{context} || $self->context)->{format}{number}); if ($format) { $n = sprintf($format,$n); if ($format =~ m/#\s*$/) {$n =~ s/(\.\d*?)0*#$/$1/; $n =~ s/\.$//} Index: Complex.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Complex.pm,v retrieving revision 1.18.6.1 retrieving revision 1.18.6.2 diff -Llib/Value/Complex.pm -Llib/Value/Complex.pm -u -r1.18.6.1 -r1.18.6.2 --- lib/Value/Complex.pm +++ lib/Value/Complex.pm @@ -3,10 +3,12 @@ package Value::Complex; my $pkg = 'Value::Complex'; -use strict; + +use strict; no strict "refs"; our @ISA = qw(Value); our $i; our $pi; + # # Check that the inputs are: # one or two real numbers, or @@ -19,7 +21,7 @@ my $self = shift; my $class = ref($self) || $self; my $context = (Value::isContext($_[0]) ? shift : $self->context); my $x = shift; $x = [$x,@_] if scalar(@_) > 0; - return $x if Value::isComplex($x) || (Value::isFormula($x) && $x->{tree}->isComplex); + return $x->inContext($context) if Value::isComplex($x) || (Value::isFormula($x) && $x->{tree}->isComplex); $x = $x->data if Value::isReal($x); $x = [$x] unless ref($x) eq 'ARRAY'; $x->[1] = 0 unless defined($x->[1]); Value::Error("Can't convert ARRAY of length %d to a Complex Number",scalar(@{$x})) @@ -39,7 +41,7 @@ my $self = shift; my $class = ref($self) || $self; my $context = (Value::isContext($_[0]) ? shift : $self->context); while (scalar(@_) < 2) {push(@_,0)} - my $c = bless {data => [@_[0,1]], context => $context}, $class; + my $c = bless {$self->hash, data => [@_[0,1]], context => $context}, $class; foreach my $x (@{$c->{data}}) {$x = $context->Package("Real")->make($context,$x) unless Value::isValue($x)} return $c; } @@ -84,42 +86,44 @@ # sub add { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my ($a,$b) = $l->value; my ($c,$d) = $r->value; - return $self->make($a + $c, $b + $d); + return $self->inherit($other)->make($a + $c, $b + $d); } sub sub { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my ($a,$b) = $l->value; my ($c,$d) = $r->value; - return $self->make($a - $c, $b - $d); + return $self->inherit($other)->make($a - $c, $b - $d); } sub mult { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my ($a,$b) = $l->value; my ($c,$d) = $r->value; - return $self->make($a*$c - $b*$d, $b*$c + $a*$d); + return $self->inherit($other)->make($a*$c - $b*$d, $b*$c + $a*$d); } sub div { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my ($a,$b) = $l->value; my ($c,$d) = $r->value; my $x = $c*$c + $d*$d; Value::Error("Division by zero") if $x->value == 0; - return $self->make(($a*$c + $b*$d)/$x,($b*$c - $a*$d)/$x); + return $self->inherit($other)->make(($a*$c + $b*$d)/$x,($b*$c - $a*$d)/$x); } sub power { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my ($a,$b) = $l->value; my ($c,$d) = $r->value; - return $self->make(1,0) if ($a->value == 1 && $b->value == 0) || ($c->value == 0 && $d->value == 0); - return $self->make(0,0) if $c->value > 0 && ($a->value == 0 && $b->value == 0); + return $self->inherit($other)->make(1,0) if ($a->value == 1 && $b->value == 0) || ($c->value == 0 && $d->value == 0); + return $self->inherit($other)->make(0,0) if $c->value > 0 && ($a->value == 0 && $b->value == 0); return exp($r * log($l)) } + sub modulo { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); - return $self->make(0) if abs($r)->value == 0; # non-fuzzy check + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + return $self->inherit($other)->make(0) if abs($r)->value == 0; # non-fuzzy check my $m = Re($l/$r)->value; my $n = int($m); $n-- if $n > $m; # act as floor() rather than int() return $l - $n*$r; @@ -132,6 +136,8 @@ # my $m = $self->getFlag("period"); if (defined $m) { + $l = $l->with(period=>undef); # make sure tests below don't use period + $r = $r->with(period=>undef); if ($self->getFlag("logPeriodic")) { return 1 if abs($l)->value == 0 || abs($r)->value == 0; # non-fuzzy checks $l = log($l); $r = log($r); @@ -248,7 +254,8 @@ # atan2(z1,z2) = atan(z1/z2) sub atan2 { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + $self = $self->inherit($other); my ($a,$b) = $l->value; my ($c,$d) = $r->value; if ($c->value == 0 && $d->value == 0) { return $self->make(0,0) if ($a->value == 0 && $b->value == 0); @@ -333,7 +340,8 @@ $a->{format} = $b->{format} = $format if defined $format; my $bi = 'i'; return $a->$method($equation) if $b == 0; - $bi = CORE::abs($b)->with(format=>$format)->$method($equation,1) . 'i' if CORE::abs($b) ne 1; + $bi = CORE::abs($b)->with(format=>$format)->$method($equation,1) . 'i' + if CORE::abs($b) !~ m/^1(\.0*)?$/; $bi = '-' . $bi if $b < 0; return $bi if $a == 0; $bi = '+' . $bi if $b > 0; Index: List.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/List.pm,v retrieving revision 1.19.6.1 retrieving revision 1.19.6.2 diff -Llib/Value/List.pm -Llib/Value/List.pm -u -r1.19.6.1 -r1.19.6.2 --- lib/Value/List.pm +++ lib/Value/List.pm @@ -5,7 +5,8 @@ package Value::List; my $pkg = 'Value::List'; -use strict; + +use strict; no strict "refs"; our @ISA = qw(Value); # @@ -14,11 +15,14 @@ # sub new { my $self = shift; my $class = ref($self) || $self; + my $context = (Value::isContext($_[0]) ? shift : $self->context); + my $def = $context->lists->get("List"); my $p = shift; my $isFormula = 0; my $isSingleton = (scalar(@_) == 0 && !(Value::isValue($p) && $p->classMatch('List'))); $p = $p->data if (Value::isValue($p) && $p->classMatch('List') && scalar(@_) == 0); - $p = [$p,@_] if (ref($p) ne 'ARRAY' || scalar(@_) > 0); + $p = [] unless defined $p; + $p = [$p,@_] if ref($p) ne 'ARRAY' || scalar(@_) > 0; my $type; foreach my $x (@{$p}) { $x = Value::makeValue($x,context=>$context) unless ref($x); @@ -27,11 +31,20 @@ if (!$type) {$type = $x->type} else {$type = 'unknown' unless $type eq $x->type} } else {$type = 'unknown'} + if (!$isSingleton && $x->type eq 'List') { + $x->{open} = $def->{nestedOpen} unless $x->{open}; + $x->{close} = $def->{nestedClose} unless $x->{close}; + } } return $p->[0] if ($isSingleton && $type eq 'List' && !$p->[0]{open}); return $self->formula($p) if $isFormula; my $list = bless {data => $p, type => $type, context=>$context}, $class; - $list->{correct_ans} = $p->[0]{correct_ans} if $isSingleton && defined $p->[0]{correct_ans}; + $list->{correct_ans} = $p->[0]{correct_ans} + if $isSingleton && defined scalar(@{$p}) && defined $p->[0]{correct_ans}; + if (scalar(@{$p}) == 0) { + $list->{open} = $def->{nestedOpen}; + $list->{close} = $def->{nestedClose}; + } return $list; } Index: Context.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Context.pm,v retrieving revision 1.10.6.1 retrieving revision 1.10.6.2 diff -Llib/Value/Context.pm -Llib/Value/Context.pm -u -r1.10.6.1 -r1.10.6.2 --- lib/Value/Context.pm +++ lib/Value/Context.pm @@ -6,6 +6,7 @@ package Value::Context; my $pkg = "Value::Context"; use strict; +use UNIVERSAL; # # Create a new Context object and initialize its data lists @@ -28,7 +29,7 @@ msg => {}, # for localization }, data => { - hashes => [], + hashes => ['cmpDefaults'], arrays => ['data'], values => ['pattern','format','value'], objects => [], @@ -84,11 +85,16 @@ foreach my $data (@{$context->{data}{values}}) { $context->{$data} = {%{$self->{$data}}}; } + $context->{error}{msg} = {%{$self->{error}{msg}}}; + $context->{error}{convert} = $self->{error}{convert} + if defined $self->{error}{convert}; + $context->{name} = $self->{name}; $context->{_initialized} = 1; return $context; } + # # Returns the package name for the specificied Value object class # (as specified by the context's {value} hash, or "Value::name"). @@ -96,11 +102,19 @@ sub Package { my $context = shift; my $class = shift; return $context->{value}{$class} if defined $context->{value}{$class}; + $class =~ s/\(\)$//; + return $context->{value}{$class} if defined $context->{value}{$class}; return "Value::$class" if defined @{"Value::${class}::ISA"}; Value::Error("No such package 'Value::%s'",$class) unless $_[0]; } # +# Make these available to Contexts +# +sub isa {UNIVERSAL::isa(@_)} +sub can {UNIVERSAL::can(@_)} + +# # Make stringify produce TeX or regular strings # sub texStrings {shift->flags->set(StringifyAsTeX=>1)} @@ -131,6 +145,8 @@ while ($more && $error->{msg}{$more}) {$more = $error->{msg}{$more}} $message = sprintf($message,@args) if scalar(@args) > 0; $message .= sprintf($more,$pos->[0]+1) if $more; + while ($message && $error->{msg}{$message}) {$message = $error->{msg}{$message}} + $message = &{$error->{convert}}($message) if defined $error->{convert}; $error->{message} = $message; $error->{string} = $string; $error->{pos} = $pos; Index: String.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/String.pm,v retrieving revision 1.9.6.1 retrieving revision 1.9.6.2 diff -Llib/Value/String.pm -Llib/Value/String.pm -u -r1.9.6.1 -r1.9.6.2 --- lib/Value/String.pm +++ lib/Value/String.pm @@ -3,7 +3,7 @@ package Value::String; my $pkg = 'Value::String'; -use strict; +use strict; no strict "refs"; our @ISA = qw(Value); # Index: Union.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Union.pm,v retrieving revision 1.24.6.1 retrieving revision 1.24.6.2 diff -Llib/Value/Union.pm -Llib/Value/Union.pm -u -r1.24.6.1 -r1.24.6.2 --- lib/Value/Union.pm +++ lib/Value/Union.pm @@ -3,7 +3,9 @@ package Value::Union; my $pkg = 'Value::Union'; -use strict; + qw(Value); +======= +use strict; no strict "refs"; our @ISA = qw(Value); # @@ -104,7 +106,7 @@ return $context->Package("Set")->new($context,$x,@_) if scalar(@_) > 0 || Value::isReal($x); return $x->inContext($context) if ref($x) eq $class; $x = $context->Package("Interval")->promote($context,$x) if $x->canBeInUnion; - return $self->make($context,$x) if Value::isValue($x) && $x->isSetOfReals; + return $self->make($context,$x) if $x->isSetOfReals; Value::Error("Can't convert %s to an Interval, Set or Union",Value::showClass($x)); } @@ -230,20 +232,31 @@ } # -# True if a union is reduced +# True if a union is reduced. +# +# (In scalar context, is a pair whose first entry is true or +# false, and when true the second value is the reason the +# set is not reduced.) # sub isReduced { my $self = shift; return 1 if $self->{isReduced}; - return $self->{data}[0]->isReduced if $self->length == 1; - my $reduced = $self->reduce; - $reduced = $self->make($reduced) unless $reduced->type eq 'Union'; - return unless $reduced->length == $self->length; - my @R = $reduced->sort->value; my @S = $self->sort->value; - foreach my $i (0..$#R) { - return unless $R[$i] == $S[$i] && $R[$i]->length == $S[$i]->length; + return $self->{data}[0]->isReduced if ($self->length == 1); + my @I; my @S; my $Sn = 0; my $error; + foreach my $x (@{$self->{data}}) + {if ($x->type eq 'Interval') {push(@I,$x)} else {$Sn++; push(@S,@{$x->{data}})}} + my $U = $self->make(@I); my $sU = $U->sort; + my $S = $self->Package("Set")->new($self->context,@S); + foreach my $i (0..$sU->length-2) { + my ($x,$y) = ($sU->{data}[$i],$sU->{data}[$i+1]); + if ($x->intersects($y)) {$error = "overlaps"; last} + if (($x + $y)->reduce->type ne 'Union') {$error = "uncombined intervals"; last} } - return 1; + $error = "overlaps in sets" if !$error && $S->intersects($U); + $error = "uncombined sets" if !$error && $Sn > 1 && !$self->getFlag('reduceSets'); + $error = "repeated elements in set" if !$error && !$S->isReduced; + return $error eq "" unless $error && wantarray; + return (0,$error); } # Index: Infinity.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Infinity.pm,v retrieving revision 1.9.6.1 retrieving revision 1.9.6.2 diff -Llib/Value/Infinity.pm -Llib/Value/Infinity.pm -u -r1.9.6.1 -r1.9.6.2 --- lib/Value/Infinity.pm +++ lib/Value/Infinity.pm @@ -3,7 +3,8 @@ package Value::Infinity; my $pkg = 'Value::Infinity'; -use strict; + +use strict; no strict "refs"; our @ISA = qw(Value); # Index: Vector.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Vector.pm,v retrieving revision 1.23.2.1 retrieving revision 1.23.2.2 diff -Llib/Value/Vector.pm -Llib/Value/Vector.pm -u -r1.23.2.1 -r1.23.2.2 --- lib/Value/Vector.pm +++ lib/Value/Vector.pm @@ -5,12 +5,12 @@ package Value::Vector; my $pkg = 'Value::Vector'; -use strict; +use strict; no strict "refs"; our @ISA = qw(Value); # # Convert a value to a Vector. The value can be -# a list of numbers, or an reference to an array of numbers +# a list of numbers, or a reference to an array of numbers # a point or vector object (demote a vector) # a matrix if it is n x 1 or 1 x n # a string that parses to a vector @@ -50,16 +50,6 @@ } # -# Make sure column vector is retained -# -sub make { - my $self = shift; - my $v = $self->SUPER::make(@_); - $v->{ColumnVector} = 1 if ref($self) && $self->{ColumnVector}; - return $v; -} - -# # Try to promote arbitary data to a vector # sub promote { @@ -69,7 +59,7 @@ return $self->new($context,$x,@_) if scalar(@_) > 0 || ref($x) eq 'ARRAY'; $x = Value::makeValue($x,context=>$context); return $x->inContext($context) if ref($x) eq $class; - return $self->make($context,$x->value) if Value::classMatch($x,'Point'); + return $self->make($context,$x->value)->inherit($x->without('open','close')) if Value::classMatch($x,'Point'); Value::Error("Can't convert %s to %s",Value::showClass($x),Value::showClass($self)); } @@ -81,23 +71,23 @@ # sub add { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my @l = $l->value; my @r = $r->value; Value::Error("Can't add Vectors with different numbers of coordinates") unless scalar(@l) == scalar(@r); my @s = (); foreach my $i (0..scalar(@l)-1) {push(@s,$l[$i] + $r[$i])} - return $self->make(@s); + return $self->inherit($other)->make(@s); } sub sub { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my @l = $l->value; my @r = $r->value; Value::Error("Can't subtract Vectors with different numbers of coordinates") unless scalar(@l) == scalar(@r); my @s = (); foreach my $i (0..scalar(@l)-1) {push(@s,$l[$i] - $r[$i])} - return $self->make(@s); + return $self->inherit($other)->make(@s); } sub mult { @@ -137,13 +127,13 @@ } sub cross { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my @l = $l->value; my @r = $r->value; Value::Error("Vectors for cross product must be in 3-space") unless scalar(@l) == 3 && scalar(@r) == 3; - $self->make($l[1]*$r[2] - $l[2]*$r[1], - -($l[0]*$r[2] - $l[2]*$r[0]), - $l[0]*$r[1] - $l[1]*$r[0]); + $self->inherit($other)->make($l[1]*$r[2] - $l[2]*$r[1], + -($l[0]*$r[2] - $l[2]*$r[0]), + $l[0]*$r[1] - $l[1]*$r[0]); } # @@ -153,7 +143,13 @@ sub compare { my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); my @l = $l->value; my @r = $r->value; - return scalar(@l) <=> scalar(@r) unless scalar(@l) == scalar(@r); + if (scalar(@l) != scalar(@r)) { + return scalar(@l) <=> scalar(@r) unless + ($l->getFlag("ijk") || $r->getFlag("ijk")) && + ($l->getFlag("ijkAnyDimension") || $r->getFlag("ijkAnyDimension")); + if (scalar(@l) < scalar(@r)) {push(@l,0 x (scalar(@r)-scalar(@l)))} + else {push(@r,0 x (scalar(@l)-scalar(@r)))} + } my $cmp = 0; foreach my $i (0..scalar(@l)-1) { $cmp = $l[$i] <=> $r[$i]; @@ -193,7 +189,13 @@ my $context = $self->context; my $sameDirection = shift; my @u = $U->value; my @v = $V->value; - return 0 unless scalar(@u) == scalar(@v); + if (scalar(@u) != scalar(@v)) { + return 0 unless + ($U->getFlag("ijk") || $V->getFlag("ijk")) && + ($U->getFlag("ijkAnyDimension") || $V->getFlag("ijkAnyDimension")); + if (scalar(@u) < scalar(@v)) {push(@u,0 x (scalar(@v)-scalar(@u)))} + else {push(@v,0 x (scalar(@u)-scalar(@v)))} + } my $k = ''; # will be scaling factor for u = k v foreach my $i (0..$#u) { # @@ -300,4 +302,3 @@ ########################################################################### 1; - Index: Set.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Set.pm,v retrieving revision 1.10.6.1 retrieving revision 1.10.6.2 diff -Llib/Value/Set.pm -Llib/Value/Set.pm -u -r1.10.6.1 -r1.10.6.2 --- lib/Value/Set.pm +++ lib/Value/Set.pm @@ -3,7 +3,8 @@ package Value::Set; my $pkg = 'Value::Set'; -use strict; + +use strict; no strict "refs"; our @ISA = qw(Value); # Convert a value to a Set. The value can be @@ -84,10 +85,11 @@ # # -# Addition forms additional sets +# Addition forms unions (or combines sets) # sub add { my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + return $self->make($l->value,$r->value) if $l->type eq 'Set' && $r->type eq 'Set'; Value::Union::form($self->context,$l,$r); } sub dot {my $self = shift; $self->add(@_)} @@ -137,7 +139,7 @@ } elsif ($x < $b) { my $context = $self->context; push(@union,$context->Package("Interval")->make($context,$I->{open},$a,$x,')')); - $I->{open} = '('; $I->{data}[0] = $x; + $I->{open} = '('; $I->{data}[0] = $a = $x; } else { $I->{close} = ')' if ($x == $b); last; @@ -207,12 +209,18 @@ } # -# True if the set is reduced +# True if a union is reduced. +# +# (In scalar context, is a pair whose first entry is true or +# false, and when true the second value is the reason the +# set is not reduced.) # sub isReduced { my $self = shift; return 1 if $self->{isReduced} || $self->length < 2; - return $self->reduce->length == $self->length; + my $isReduced = $self->reduce->length == $self->length; + return $isReduced if $isReduced || !wantarray; + return (0,"repeated elements"); } # Index: AnswerChecker.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/AnswerChecker.pm,v retrieving revision 1.91.2.1 retrieving revision 1.91.2.2 diff -Llib/Value/AnswerChecker.pm -Llib/Value/AnswerChecker.pm -u -r1.91.2.1 -r1.91.2.2 --- lib/Value/AnswerChecker.pm +++ lib/Value/AnswerChecker.pm @@ -1,3 +1,4 @@ + =head1 DESCRIPTION ############################################################# @@ -25,10 +26,19 @@ # $Value::defaultContext->{cmpDefaults} = {}; +=head5 $mathObject->cmp_defaults() + +# Internal use. +# Set default flags for the answer checker in this object +# showTypeWarnings => 1 +# showEqualErrors => 1 +# ignoreStrings => 1 +# studentsMustReduceUnions => 1 +# show UnionReduceWarnings => 1 +# + +=cut -# -# Default flags for the answer checkers -# sub cmp_defaults {( showTypeWarnings => 1, showEqualErrors => 1, @@ -37,9 +47,11 @@ showUnionReduceWarnings => 1, )} + # # Special Context flags to be set for the student answer # + sub cmp_contextFlags { my $self = shift; my $ans = shift; return ( @@ -62,10 +74,11 @@ # # Create an answer checker for the given type of object # + sub cmp { my $self = shift; my $ans = new AnswerEvaluator; - my $correct = protectHTML($self->{correct_ans}); + my $correct = preformat($self->{correct_ans}); $correct = $self->correct_ans unless defined($correct); $self->{context} = Value->context unless defined($self->{context}); $ans->ans_hash( @@ -74,11 +87,12 @@ correct_value => $self, $self->cmp_defaults(@_), %{$self->{context}{cmpDefaults}{$self->class} || {}}, # context-specified defaults - @_ + @_, ); $ans->{debug} = $ans->{rh_ans}{debug}; + $ans->install_evaluator(sub { - $ans = shift; + my $ans = shift; $ans->{_filter_name} = "MathObjects answer checker"; $ans->{correct_value}->cmp_parse($ans); }); @@ -87,7 +101,7 @@ return $ans; } -sub correct_ans {protectHTML(shift->string)} +sub correct_ans {preformat(shift->string)} sub cmp_diagnostics {} # @@ -104,13 +118,16 @@ my $context = $ans->{correct_value}{context} || $current; Parser::Context->current(undef,$context); # change to correct answser's context my $flags = contextSet($context,$self->cmp_contextFlags($ans)); # save old context flags - my $inputs = $self->getPG('$inputs_ref',{action=>""}); + my $inputs = $self->getPG('$inputs_ref'); $ans->{isPreview} = $inputs->{previewAnswers} || ($inputs->{action} =~ m/^Preview/); $ans->{cmp_class} = $self->cmp_class($ans) unless $ans->{cmp_class}; $ans->{error_message} = $ans->{ans_message} = ''; # clear any old messages $ans->{preview_latex_string} = $ans->{preview_text_string} = ''; $context->clearError(); - $contest->{answerHash} = $ans; # values here can override context flags + $context->{answerHash} = $ans; # values here can override context flags + + $context->{answerHash} = $ans; # values here can override context flags + # # Parse and evaluate the student answer @@ -129,16 +146,16 @@ unless Value::isValue($ans->{student_value}); $ans->{student_value}{isStudent} = 1; $ans->{preview_latex_string} = $ans->{student_formula}->TeX; - $ans->{preview_text_string} = protectHTML($ans->{student_formula}->string); + $ans->{preview_text_string} = preformat($ans->{student_formula}->string); # # Get the string for the student answer # - for ($ans->{formatStudentAnswer} || $context->flag('formatStudentAnswer')) { - /evaluated/i and do {$ans->{student_ans} = protectHTML($ans->{student_value}->string); last}; + for ($self->getFlag('formatStudentAnswer')) { + /evaluated/i and do {$ans->{student_ans} = preformat($ans->{student_value}->string); last}; /parsed/i and do {$ans->{student_ans} = $ans->{preview_text_string}; last}; /reduced/i and do { my $oldFlags = contextSet($context,reduceConstants=>1,reduceConstantFunctions=>0); - $ans->{student_ans} = protectHTML($ans->{student_formula}->substitute()->string); + $ans->{student_ans} = preformat($ans->{student_formula}->substitute()->string); contextSet($context,%{$oldFags}); last; }; warn "Unkown student answer format |$ans->{formatStudentAnswer}|"; @@ -224,10 +241,13 @@ sub cmp_compare { my $self = shift; my $other = shift; my $ans = shift; my $nth = shift || ''; + my $context = (Value::isValue($self) ? $self->context : Value->context); return eval {$self == $other} unless ref($ans->{checker}) eq 'CODE'; my @equal = eval {&{$ans->{checker}}($self,$other,$ans,$nth,@_)}; - if (!defined($equal) && $@ ne '' && (!$self->context->{error}{flag} || $ans->{showAllErrors})) { - $self->context->setError(["<I>An error occurred while checking your$nth answer:</I>\n". + + if (!defined($equal) && $@ ne '' && (!$context->{error}{flag} || $ans->{showAllErrors})) { + $context->setError(["<I>An error occurred while checking your$nth answer:</I>\n". + '<DIV STYLE="margin-left:1em">%s</DIV>',$@],'',undef,undef,$CMP_ERROR); warn "Please inform your instructor that an error occurred while checking your answer"; } @@ -317,21 +337,17 @@ return unless $ans->{studentsMustReduceUnions} && $ans->{showUnionReduceWarnings} && !$ans->{isPreview} && !Value::isFormula($student); - if ($student->type eq 'Union' && $student->length >= 2) { - my $reduced = $student->reduce; - return "Your$nth union can be written without overlaps" - unless $reduced->type eq 'Union' && $reduced->length == $student->length; - my @R = $reduced->sort->value; - my @S = $student->sort->value; - foreach my $i (0..$#R) { - return "Your$nth union can be written without overlaps" - unless $R[$i] == $S[$i] && $R[$i]->length == $S[$i]->length; - } - } elsif ($student->type eq 'Set' && $student->length >= 2) { - return "Your$nth set should have no repeated elements" - unless $student->reduce->length == $student->length; - } - return; + return unless $student->isSetOfReals; + my ($result,$error) = $student->isReduced; + return unless $error; + return { + "overlaps" => "Your$nth union contains overlapping intervals", + "overlaps in sets" => "Your$nth union contains sets and intervals that overlap", + "uncombined intervals" => "Your$nth union can be simplified by combining intervals", + "uncombined sets" => "Your$nth union can be simplified by combining some sets", + "repeated elements in set" => "Your$nth union contains sets with repeated elements", + "repeated elements" => "Your$nth set should have no repeated elements", + }->{$error}; } # @@ -592,13 +608,22 @@ # Quote HTML characters # sub protectHTML { - my $string = shift; - return unless defined($string); - return $string if eval ('$main::displayMode') eq 'TeX'; - $string =~ s/&/\&/g; - $string =~ s/</\</g; - $string =~ s/>/\>/g; - $string; + my $string = shift; + return unless defined($string); + return $string if eval ('$main::displayMode') eq 'TeX'; + $string =~ s/&/\&/g; + $string =~ s/</\</g; + $string =~ s/>/\>/g; + $string; +} + +# +# Convert newlines to <BR> +# +sub preformat { + my $string = protectHTML(shift); + $string =~ s!\n!<br />!g unless eval('$main::displayMode') eq 'TeX'; + $string; } # @@ -846,8 +871,11 @@ return unless $ans->{score} == 0 && !$ans->{isPreview}; my $student = $ans->{student_value}; return if $ans->{ignoreStrings} && (!Value::isValue($student) || $student->type eq 'String'); - if ($ans->{showDimensionHints} && $self->length != $student->length) { - $self->cmp_Error($ans,"The number of coordinates is incorrect"); return; + if ($self->length != $student->length) { + ($self,$student) = $self->cmp_pad($student); + if ($ans->{showDimensionHints} && $self->length != $student->length) { + $self->cmp_Error($ans,"The number of coordinates is incorrect"); return; + } } if ($ans->{parallel} && !$student->isFormula && !$student->classMatch('String') && $self->isParallel($student,$ans->{sameDirection})) { @@ -863,6 +891,23 @@ } } +# +# Pad the student or correct answer if either is in ijk notation +# and they are not the same dimension. Only add zeros when the other one +# also has zeros in those places. +# +sub cmp_pad { + my $self = shift; my $student = shift; + if (($self->getFlag("ijk") || $student->getFlag("ijk")) && $self->getFlag("ijkAnyDimension")) { + $self = $self->copy; $student = $student->copy; + while ($self->length > $student->length && $self->{data}[$student->length] == 0) + {push(@{$student->{data}},Value::Real->new(0))} + while ($self->length < $student->length && $student->{data}[$self->length] == 0) + {push(@{$self->{data}},Value::Real->new(0))} + } + return ($self,$student); +} + sub correct_ans { my $self = shift; return $self->SUPER::correct_ans unless $self->{ans_name}; @@ -1089,8 +1134,10 @@ # sub cmp_equal { my ($self,$ans) = @_; - return Value::List::cmp_equal(@_) if $ans->{student_value}->type eq 'Set'; - $self->SUPER::cmp_equal($ans); + return $self->SUPER::cmp_equal($ans) unless $ans->{student_value}->type eq 'Set'; + my $error = $self->cmp_checkUnionReduce($ans->{student_value},$ans); + if ($error) {$self->cmp_Error($ans,$error); return} + return Value::List::cmp_equal(@_); } # @@ -1575,9 +1622,48 @@ $cmp->ans_hash(correct_value => $f); Parser::Context->current(undef,$current); } + $cmp->install_pre_filter(\&Value::Formula::cmp_call_filter,"cmp_prefilter"); + $cmp->install_post_filter(\&Value::Formula::cmp_call_filter,"cmp_postfilter"); return $cmp; } +sub cmp_call_filter { + my $ans = shift; my $method = shift; + return $ans->{correct_value}->$method($ans); +} + +sub cmp_prefilter { + my $self = shift; my $ans = shift; + $ans->{_filter_name} = "fetch_previous_answer"; + $ans->{prev_ans} = undef; + if (defined($ans->{ans_label})) { + my $label = "previous_".$ans->{ans_label}; + my $inputs = $self->getPG('$inputs_ref'); + if (defined $inputs->{$label} and $inputs->{$label} =~ /\S/) { + $ans->{prev_ans} = $inputs->{$label}; + #FIXME -- previous answer item is not always being updated in inputs_ref (which comes from formField) + } + } + return $ans; +} + +sub cmp_postfilter { + my $self = shift; my $ans = shift; + $ans->{_filter_name} = "produce_equivalence_message"; + return $ans if $ans->{ans_message}; # don't overwrite other messages + $ans->{prev_formula} = Parser::Formula($self->{context},$ans->{prev_ans}); + if (defined($ans->{prev_formula}) && defined($ans->{student_formula})) { + my $prev = eval {$self->promote($ans->{prev_formula})}; break unless defined($prev); + $ans->{prev_equals_current} = Value::cmp_compare($prev,$ans->{student_formula},{}); + if ( !$ans->{isPreview} # not preview mode + and $ans->{prev_equals_current} # equivalent + and $ans->{prev_ans} ne $ans->{original_student_ans}) # but not identical + {$ans->{ans_message} = "This answer is equivalent to the one you just submitted."} + } + return $ans; +} + + sub cmp_equal { my $self = shift; my $ans = shift; # @@ -1970,7 +2056,10 @@ # sub value { my $self = shift; + + return $self unless defined $self->{tree}{coords}; my $context = $self->context; + my @array = (); if ($self->{tree}->type eq 'Matrix') { foreach my $row (@{$self->{tree}->coords}) { Index: Formula.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Formula.pm,v retrieving revision 1.43.2.1 retrieving revision 1.43.2.2 diff -Llib/Value/Formula.pm -Llib/Value/Formula.pm -u -r1.43.2.1 -r1.43.2.2 --- lib/Value/Formula.pm +++ lib/Value/Formula.pm @@ -5,14 +5,17 @@ package Value::Formula; my $pkg = 'Value::Formula'; -use strict; +use strict; no strict "refs"; our @ISA = qw(Parser Value); + + my $UNDEF = bless {}, "UNDEF"; # used for undefined points # # Call Parser to make the new Formula + # sub new { my $self = shift; @@ -32,11 +35,11 @@ # as the formula itself. # sub with { - my $self = (shift)->copy; my %hash = @_; - foreach my $id (keys(%hash)) { - $self->{tree}{$id} = $hash{$id}; - $self->{$id} = $hash{$id}; - } + + my $self = shift; my %hash = @_; + $self = $self->SUPER::with(@_); + $self->{tree} = $self->{tree}->copy($self); # make a new copy pointing to the new equation. + foreach my $id (keys(%hash)) {$self->{tree}{$id} = $hash{$id}} return $self; } @@ -76,13 +79,14 @@ $formula->{context} = $r->{context}; $r = $r->{tree}->copy($formula); } else { - $r = $self->new($r)->{tree}; + $r = $self->new($r)->{tree}->copy($formula); } if (ref($l) eq $class || ref($l) eq $pkg) { $formula->{context} = $l->{context}; $l = $l->{tree}->copy($formula); + } else { - $l = $self->new($l)->{tree}; + $l = $self->new($l)->{tree}->copy($formula); } $bop = 'U' if $bop eq '+' && ($l->type =~ m/Interval|Set|Union/ || $r->type =~ m/Interval|Set|Union/); @@ -183,6 +187,11 @@ # to the ORIGINAL correct answer. (This will have to be # fixed if we ever do adaptive parameters for non-real formulas) # + # FIXME: it doesn't make sense to apply the ORIGINAL value's + # tolerance, and causes problems when the values + # differ in magnitude by much. Gavin has found several + # situations where this is a problem. + # if ($l->AdaptParameters($r,$self->{context}->variables->parameters)) { my $avalues = $l->{test_adapt}; my $tolerance = $self->getFlag('tolerance',1E-4); @@ -413,53 +422,60 @@ # sub AdaptParameters { my $l = shift; my $r = shift; - my @params = @_; my $d = scalar(@params); + my @params = @_; my $d = scalar(@params); my $D; return 0 if $d == 0; return 0 unless $l->usesOneOf(@params); $l->Error("Adaptive parameters can only be used for real-valued formulas") unless $l->{tree}->isRealNumber; + # - # Get coefficient matrix of adaptive parameters - # and value vector for linear system + # Try up to three times (the random points might not work the first time) # - my ($p,$v) = $l->createRandomPoints($d); - my @P = (0) x $d; my ($f,$F) = ($l->{f},$r->{f}); - my @A = (); my @b = (); - foreach my $i (0..$d-1) { - my @a = (); my @p = @{$p->[$i]}; - foreach my $j (0..$d-1) { - $P[$j] = 1; push(@a,(&$f(@p,@P)-$v->[$i])->value); - $P[$j] = 0; + foreach my $attempt (1..3) { + # + # Get coefficient matrix of adaptive parameters + # and value vector for linear system + # + my ($p,$v) = $l->createRandomPoints($d); + my @P = (0) x $d; my ($f,$F) = ($l->{f},$r->{f}); + my @A = (); my @b = (); + foreach my $i (0..$d-1) { + my @a = (); my @p = @{$p->[$i]}; + foreach my $j (0..$d-1) { + $P[$j] = 1; push(@a,(&$f(@p,@P)-$v->[$i])->value); + $P[$j] = 0; + } + push @A, [@a]; push @b, [(&$F(@p,@P)-$v->[$i])->value]; } - push @A, [@a]; push @b, [(&$F(@p,@P)-$v->[$i])->value]; - } - # - # Use MatrixReal1.pm to solve system of linear equations - # - my $M = MatrixReal1->new($d,$d); $M->[0] = \@A; - my $B = MatrixReal1->new($d,1); $B->[0] = \@b; - ($M,$B) = $M->normalize($B); - $M = $M->decompose_LR; - if (($d,$B,$M) = $M->solve_LR($B)) { - if ($d == 0) { - # - # Get parameter values and recompute the points using them - # - my @a; my $i = 0; my $max = $l->getFlag('max_adapt',1E8); - foreach my $row (@{$B->[0]}) { - if (abs($row->[0]) > $max) { - $max = Value::makeValue($max); $row->[0] = Value::makeValue($row->[0]); - $l->Error(["Constant of integration is too large: %s\n(maximum allowed is %s)", - $row->[0]->string,$max->string]) if $params[$i] eq 'C0'; - $l->Error(["Adaptive constant is too large: %s = %s\n(maximum allowed is %s)", - $params[$i],$row->[0]->string,$max->string]); - } - push @a, $row->[0]; $i++; + + # + # Use MatrixReal1.pm to solve system of linear equations + # + my $M = MatrixReal1->new($d,$d); $M->[0] = \@A; + my $B = MatrixReal1->new($d,1); $B->[0] = \@b; + ($M,$B) = $M->normalize($B); + $M = $M->decompose_LR; + if (($D,$B,$M) = $M->solve_LR($B)) { + if ($D == 0) { + # + # Get parameter values and recompute the points using them + # + my @a; my $i = 0; my $max = $l->getFlag('max_adapt',1E8); + foreach my $row (@{$B->[0]}) { + if (abs($row->[0]) > $max) { + $max = Value::makeValue($max); $row->[0] = Value::makeValue($row->[0]); + $l->Error(["Constant of integration is too large: %s\n(maximum allowed is %s)", + $row->[0]->string,$max->string]) if $params[$i] eq 'C0' or $params[$i] eq 'n00'; + $l->Error(["Adaptive constant is too large: %s = %s\n(maximum allowed is %s)", + $params[$i],$row->[0]->string,$max->string]); + } + push @a, $row->[0]; $i++; + } + my $context = $l->context; + foreach my $i (0..$#a) {$context->{variables}{$params[$i]}{value} = $a[$i]} + $l->{parameters} = [@a]; + $l->createAdaptedValues; + return 1; } - my $context = $l->context; - foreach my $i (0..$#a) {$context->{variables}{$params[$i]}{value} = $a[$i]} - $l->{parameters} = [@a]; - $l->createAdaptedValues; - return 1; } } $l->Error("Can't solve for adaptive parameters"); Index: Interval.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Interval.pm,v retrieving revision 1.28.6.1 retrieving revision 1.28.6.2 diff -Llib/Value/Interval.pm -Llib/Value/Interval.pm -u -r1.28.6.1 -r1.28.6.2 --- lib/Value/Interval.pm +++ lib/Value/Interval.pm @@ -5,9 +5,10 @@ package Value::Interval; my $pkg = 'Value::Interval'; -use strict; +use strict; no strict "refs"; our @ISA = qw(Value); + # # Convert a value to an interval. The value consists of # an open paren string, one or two real numbers or infinities, @@ -15,14 +16,15 @@ # sub new { my $self = shift; my $class = ref($self) || $self; + my $context = (Value::isContext($_[0]) ? shift : $self->context); - if (scalar(@_) == 1 && (!ref($_[0]) || ref($_[0]) eq 'ARRAY')) { + if (scalar(@_) == 1) { my $x = Value::makeValue($_[0],context=>$context); if (Value::isFormula($x)) { return $x if $x->type eq 'Interval'; Value::Error("Formula does not return an Interval"); } - return $self->promote($x); + return $self->promote($context,$x); } my @params = @_; Value::Error("Interval can't be empty") unless scalar(@params) > 0; @@ -30,7 +32,8 @@ return $context->Package("Set")->new($context,@params) if scalar(@params) == 1; @params = ('(',@params,')') if (scalar(@params) == 2); my ($open,$a,$b,$close) = @params; - if (!defined($close)) {$close = $b; $b = $a} + ($b,$close) = ($a,$b) unless defined($close); + ($a,$b,$open) = ($open,$a,$b) if !ref($b) && ($b eq '(' || $b eq '['); $a = Value::makeValue($a,context=>$context); $b = Value::makeValue($b,context=>$context); return $self->formula($open,$a,$b,$close) if Value::isFormula($a) || Value::isFormula($b); Value::Error("Endpoints of intervals must be numbers or infinities") unless @@ -52,6 +55,7 @@ if $a == $b && ($open ne '[' || $close ne ']'); return $context->Package("Set")->new($context,$a) if $a == $b; bless { + $self->hash, data => [$a,$b], open => $open, close => $close, leftInfinite => $nia, rightInfinite => $ib, context => $context, @@ -65,7 +69,9 @@ my $self = shift; my $class = ref($self) || $self; my $context = (Value::isContext($_[0]) ? shift : $self->context); my ($open,$a,$b,$close) = @_; - $close = $b, $b = $a unless defined($close); + ($open,$a,$b,$close) = ("(",$open,$a,")") unless defined($b); + ($b,$close) = ($a,$b) unless defined($close); + ($a,$b,$open) = ($open,$a,$b) if !ref($b) && ($b eq '(' || $b eq '['); bless { data => [$a,$b], open => $open, close => $close, leftInfinite => isNegativeInfinity($a), rightInfinite => isInfinity($b), @@ -151,7 +157,7 @@ my $x = (scalar(@_) ? shift : $self); return $self->new($context,$x,@_) if scalar(@_) > 0; $x = Value::makeValue($x,context=>$context); - return $x if $x->isSetOfReals; + return $x->inContext($context) if $x->isSetOfReals; return $context->Package("Set")->new($context,$x) if Value::isReal($x); my $open = $x->{open}; $open = '(' unless defined($open); my $close = $x->{close}; $close = ')' unless defined($close); Index: Matrix.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Matrix.pm,v retrieving revision 1.22.4.1 retrieving revision 1.22.4.2 diff -Llib/Value/Matrix.pm -Llib/Value/Matrix.pm -u -r1.22.4.1 -r1.22.4.2 --- lib/Value/Matrix.pm +++ lib/Value/Matrix.pm @@ -7,9 +7,10 @@ package Value::Matrix; my $pkg = 'Value::Matrix'; -use strict; +use strict; no strict "refs"; our @ISA = qw(Value); + # # Convert a value to a matrix. The value can be: # a list of numbers or list of (nested) references to arrays of numbers, @@ -175,27 +176,28 @@ # sub add { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my @l = @{$l->data}; my @r = @{$r->data}; Value::Error("Can't add Matrices with different dimensions") unless scalar(@l) == scalar(@r); my @s = (); foreach my $i (0..scalar(@l)-1) {push(@s,$l[$i] + $r[$i])} - return $self->make(@s); + return $self->inherit($other)->make(@s); } sub sub { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my @l = @{$l->data}; my @r = @{$r->data}; Value::Error("Can't subtract Matrices with different dimensions") unless scalar(@l) == scalar(@r); my @s = (); + foreach my $i (0..scalar(@l)-1) {push(@s,$l[$i] - $r[$i])} - return $self->make(@s); + return $self->inherit($other)->make(@s); } sub mult { - my ($l,$r,$flag) = @_; my $self = $l; + my ($l,$r,$flag) = @_; my $self = $l; my $other = $r; # # Constant multiplication # @@ -230,6 +232,7 @@ } push(@M,$self->make(@row)); } + $self = $self->inherit($other) if Value::isValue($other); return $self->make(@M); } Index: Point.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Point.pm,v retrieving revision 1.18.6.1 retrieving revision 1.18.6.2 diff -Llib/Value/Point.pm -Llib/Value/Point.pm -u -r1.18.6.1 -r1.18.6.2 --- lib/Value/Point.pm +++ lib/Value/Point.pm @@ -5,9 +5,10 @@ package Value::Point; my $pkg = 'Value::Point'; -use strict; +use strict; no strict "refs"; our @ISA = qw(Value); + # # Convert a value to a point. The value can be # a list of numbers, or an reference to an array of numbers @@ -60,23 +61,26 @@ # sub add { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my @l = $l->value; my @r = $r->value; Value::Error("Can't add Points with different numbers of coordinates") unless scalar(@l) == scalar(@r); my @s = (); foreach my $i (0..scalar(@l)-1) {push(@s,$l[$i] + $r[$i])} - return $self->make(@s); + return $self->inherit($other)->make(@s); } sub sub { - my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); my @l = $l->value; my @r = $r->value; Value::Error("Can't subtract Points with different numbers of coordinates") unless scalar(@l) == scalar(@r); my @s = (); foreach my $i (0..scalar(@l)-1) {push(@s,$l[$i] - $r[$i])} - return $self->make(@s); + return $self->inherit($other)->make(@s); + } sub mult { Index: Data.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Context/Data.pm,v retrieving revision 1.9.6.1 retrieving revision 1.9.6.2 diff -Llib/Value/Context/Data.pm -Llib/Value/Context/Data.pm -u -r1.9.6.1 -r1.9.6.2 --- lib/Value/Context/Data.pm +++ lib/Value/Context/Data.pm @@ -4,6 +4,7 @@ # package Value::Context::Data; use strict; +use Scalar::Util; sub new { my $self = shift; my $class = ref($self) || $self; @@ -17,6 +18,7 @@ namePattern => '', # pattern for allowed names for new items name => '', Name => '', # lower- and upper-case names for the class of items }, $class; + $data->weaken; $data->init(); $parent->{$data->{dataName}} = {}; push @{$parent->{data}{objects}},"_$data->{dataName}"; @@ -47,6 +49,7 @@ $copy->{$name} = $data->{$name}; } } + $self->{tokens} = {%{$orig->{tokens}}}; foreach my $p (keys %{$orig->{patterns}}) { $self->{patterns}{$p} = @@ -55,6 +58,11 @@ } # +# Make context pointer a weak pointer (avoids reference loops) +# +sub weaken {Scalar::Util::weaken((shift)->{context})} + +# # Update the context patterns # sub update {(shift)->{context}->update} --- /dev/null +++ lib/Applet.pm @@ -0,0 +1,673 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: pg/lib/Applet.pm,v 1.12.2.1 2008/06/23 23:44:48 gage Exp $ +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the +# Artistic License for more details. +################################################################################ + +=head1 NAME + +Applet.pl - Provides code for inserting FlashApplets and JavaApplets into webwork problems + +=head1 SYNPOSIS + + ################################### + # Create link to applet + ################################### + my $appletName = "LineThruPointsWW"; + $applet = new FlashApplet( + # can be replaced by $applet =FlashApplet() when using AppletObjects.pl + codebase => findAppletCodebase("$appletName.swf"), + appletName => $appletName, + appletId => $appletName, + submitActionAlias => 'checkAnswer', + ); + + ################################### + # Configure applet + ################################### + + #xml data to set up the problem-rac + $applet->config(qq{<XML> + <point xval='$xval_1' yval='$yval_1' /> + <point xval='$xval_2' yval='$yval_2' /> + </XML>}); + + + ################################### + # insert applet header material + ################################### + HEADER_TEXT($applet->insertHeader ); + + ################################### + # Text section + # + + ################################### + #insert applet into body + ################################### + TEXT( MODES(TeX=>'object code', HTML=>$applet->insertObject)); + + +=head1 DESCRIPTION + +This file provides an object to store in one place +all of the information needed to call an applet. + +The object FlashApplet has defaults for inserting flash applets. + +=over + +=item * + +=item * + +=back + +(not yet completed) + +The module JavaApplet has defaults for inserting java applets. + +The module Applet stores common code for the two types of applet. + +=head1 USAGE + +These modules are activate by listing it in the modules section of global.conf and rebooting the server. +The companion file to this one is macros/AppletObjects.pl + +qw(Applet FlashApplet JavaApplet) + +=cut + + + +package Applet; + +use URI::Escape; + + + +use MIME::Base64 qw( encode_base64 decode_base64); + + +=head2 Default javaScript functions placed in header + +These functions are automatically defined for use for +any javaScript placed in the text of a PG question. + + getApplet(appletName) -- finds the applet path in the DOM + + submitAction() -- calls the submit action of the applets + + + initializeAction() -- calls the initialize action of the applets + + getQE(name) -- gets an HTML element of the question by name + or by id. Be sure to keep all names and ids + unique within a given PG question. + + getQuestionElement(name) -- long form of getQE(name) + + listQuestionElements() -- for discovering the names of inputs in the + PG question. An alert dialog will list all + of the elements. + Usage: Place this at the END of the question, + just before END_DOCUMENT(): + + TEXT(qq!<script> listQuestionElements() </script>!); + ENDDOCUMENT(); + + list of accessor methods format: current_value = $self->method(new_value or empty) + + appletId for simplicity and reliability appletId and appletName are always the same + appletName + + archive the name of the .jar file containing the applet code + code the name of the applet code in the .jar archive + codebase a prefix url used to find the archive and the applet itself + + height rectangle alloted in the html page for displaying the applet + width + + params an anonymous array containing name/value pairs + to configure the applet [name =>'value, ...] + + header stores the text to be added to the header section of the html page + object stores the text which places the applet on the html page + + debug in debug mode several alerts mark progress through the procedure of calling the applet + + config configuration are those customizable attributes of the applet which don't + change as it is used. When stored in hidden answer fields + it is usually stored in base64 encoded format. + base64_config base64 encode version of the contents of config + + configAlias (default: config ) names the applet command called with the contents of $self->config + to configure the applet. The parameters are passed to the applet in plain text using <xml> + The outer tags must be <xml> ..... </xml> + state state consists of those customizable attributes of the applet which change + as the applet is used. It is stored by the calling .pg question so that + when revisiting the question the applet + will be restored to the same state it was left in when the question was last + viewed. + + getStateAlias (default: getState) alias for command called to read the current state of the applet. + The state is passed in plain text xml format with outer tags: <xml>....</xml> + setStateAlias (default: setState) alias for the command called to reset the state of the applet. + The state is passed in plain text in xml format with outer tags: <xml>....</xml> + + base64_state returns the base64 encoded version of the state stored in the applet object. + + initializeActionAlias -- (default: initializeAction) the name of the javaScript subroutine called to initialize the applet (some overlap with config/ and setState + submitActionAlias -- (default: submitAction)the name of the javaScript subroutine called when the submit button of the + .pg question is pressed. + + returnFieldName + + + + + +=cut + + + + +sub new { + my $class = shift; + my $self = { + appletName =>'', + code=>'', + codebase=>'', +# appletId =>'', #always use identical applet Id's and applet Names + params =>undef, + width => 550, + height => 400, + base64_state => undef, # this is an state to use for initializing the first occurence of the question. + base64_config => undef, # this is the initial (and final?) configuration + getStateAlias => 'getXML', + setStateAlias => 'setXML', + configAlias => 'config', + initializeActionAlias => 'setXML', + submitActionAlias => 'getXML', + returnFieldName => 'receivedField', + headerText => DEFAULT_HEADER_TEXT(), + objectText => '', + debug => 0, + @_, + }; + bless $self, $class; + $self->state('<xml></xml>'); + $self->config('<xml></xml>'); + return $self; +} + +sub header { + my $self = shift; + if ($_[0] eq "reset") { # $applet->header('reset'); erases default header text. + $self->{headerText}=''; + } else { + $self->{headerText} .= join("",@_); # $applet->header(new_text); concatenates new_text to existing header. + } + $self->{headerText}; +} +sub object { + my $self = shift; + if ($_[0] eq "reset") { + $self->{objectText}=''; + } else { + $self->{objectText} .= join("",@_); + } + $self->{objectText}; +} +sub params { + my $self = shift; + if (ref($_[0]) =~/HASH/) { + $self->{params} = shift; + } elsif ( !defined($_[0]) or $_[0] =~ '') { + # do nothing (read) + } else { + warn "You must enter a reference to a hash for the parameter list"; + } + $self->{params}; +} + +sub initializeActionAlias { + my $self = shift; + $self->{initializeActionAlias} = shift ||$self->{initializeActionAlias}; # replace the current contents if non-empty + $self->{initializeActionAlias}; +} + +sub submitActionAlias { + my $self = shift; + $self->{submitActionAlias} = shift ||$self->{submitActionAlias}; # replace the current contents if non-empty + $self->{submitActionAlias}; +} +sub getStateAlias { + my $self = shift; + $self->{getStateAlias} = shift ||$self->{getStateAlias}; # replace the current contents if non-empty + $self->{getStateAlias}; +} + +sub setStateAlias { + my $self = shift; + $self->{setStateAlias} = shift ||$self->{setStateAlias}; # replace the current contents if non-empty + $self->{setStateAlias}; +} +sub configAlias { + my $self = shift; + $self->{configAlias} = shift ||$self->{configAlias}; # replace the current contents if non-empty + $self->{configAlias}; +} +sub returnFieldName { + my $self = shift; + $self->{returnFieldName} = shift ||$self->{returnFieldName}; # replace the current contents if non-empty + $self->{returnFieldName}; +} +sub codebase { + my $self = shift; + $self->{codebase} = shift ||$self->{codebase}; # replace the current ... [truncated message content] |