[system] / trunk / pg / lib / Value / AnswerChecker.pm Repository:
ViewVC logotype

View of /trunk/pg/lib/Value/AnswerChecker.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2916 - (download) (as text) (annotate)
Wed Oct 13 18:41:07 2004 UTC (8 years, 7 months ago) by dpvc
File size: 22636 byte(s)
Clear error messages before doing the answer check (this clears old
messages left over inclass this checker is called by another one,
e.g. when using UNORDERD_ANS()).

    1 #############################################################
    2 #
    3 #  Implements the ->cmp method for Value objects.  This produces
    4 #  an answer checker appropriate for the type of object.
    5 #  Additional options can be passed to the checker to
    6 #  modify its action.
    7 #
    8 #  The individual Value packages are modified below to add the
    9 #  needed methods.
   10 #
   11 
   12 #############################################################
   13 
   14 package Value;
   15 
   16 #
   17 #  Create an answer checker for the given type of object
   18 #
   19 
   20 sub cmp_defaults {(
   21   showTypeWarnings => 1,
   22   showEqualErrors  => 1,
   23   ignoreStrings    => 1,
   24 )}
   25 
   26 sub cmp {
   27   my $self = shift;
   28   my $ans = new AnswerEvaluator;
   29   $ans->ans_hash(
   30     type => "Value (".$self->class.")",
   31     correct_ans => protectHTML($self->string),
   32     correct_value => $self,
   33     $self->cmp_defaults,
   34     @_
   35   );
   36   $ans->install_evaluator(sub {$ans = shift; $ans->{correct_value}->cmp_parse($ans)});
   37   $self->{context} = $$Value::context unless defined($self->{context});
   38   return $ans;
   39 }
   40 
   41 #
   42 #  Parse the student answer and compute its value,
   43 #    produce the preview strings, and then compare the
   44 #    student and professor's answers for equality.
   45 #
   46 sub cmp_parse {
   47   my $self = shift; my $ans = shift;
   48   #
   49   #  Do some setup
   50   #
   51   my $current = $$Value::context; # save it for later
   52   my $context = $ans->{correct_value}{context} || $current;
   53   Parser::Context->current(undef,$context); # change to correct answser's context
   54   my $flags = contextSet($context, # save old context flags for the below
   55     StringifyAsTeX => 0,             # reset this, just in case.
   56     no_parameters => 1,              # don't let students enter parameters
   57     showExtraParens => 1,            # make student answer painfully unambiguous
   58     reduceConstants => 0,            # don't combine student constants
   59     reduceConstantFunctions => 0,    # don't reduce constant functions
   60   );
   61   $ans->{isPreview} = $self->getPG('$inputs_ref->{previewAnswers}');
   62   $ans->{cmp_class} = $self->cmp_class($ans) unless $ans->{cmp_class};
   63   $ans->{error_message} = $ans->{ans_message} = ''; # clear any old messages
   64   $ans->{preview_latex_string} = $ans->{preview_text_string} = '';
   65 
   66   #
   67   #  Parse and evaluate the student answer
   68   #
   69   $ans->score(0);  # assume failure
   70   $ans->{student_value} = $ans->{student_formula} = Parser::Formula($ans->{student_ans});
   71   $ans->{student_value} = Parser::Evaluate($ans->{student_formula})
   72     if defined($ans->{student_formula}) && $ans->{student_formula}->isConstant;
   73 
   74   #
   75   #  If it parsed OK, save the output forms and check if it is correct
   76   #   otherwise report an error
   77   #
   78   if (defined $ans->{student_value}) {
   79     $ans->{student_value} = Value::Formula->new($ans->{student_value})
   80        unless Value::isValue($ans->{student_value});
   81     $ans->{preview_latex_string} = $ans->{student_formula}->TeX;
   82     $ans->{preview_text_string}  = protectHTML($ans->{student_formula}->string);
   83     $ans->{student_ans}          = $ans->{preview_text_string};
   84     $self->cmp_equal($ans);
   85     $self->cmp_postprocess($ans) if !$ans->{error_message};
   86   } else {
   87     $self->cmp_error($ans);
   88   }
   89   contextSet($context,%{$flags});            # restore context values
   90   Parser::Context->current(undef,$current);  # put back the old context
   91   return $ans;
   92 }
   93 
   94 #
   95 #  Check if the parsed student answer equals the professor's answer
   96 #
   97 sub cmp_equal {
   98   my $self = shift; my $ans = shift;
   99   my $correct = $ans->{correct_value};
  100   my $student = $ans->{student_value};
  101   if ($correct->typeMatch($student,$ans)) {
  102     my $equal = eval {$correct == $student};
  103     if (defined($equal) || !$ans->{showEqualErrors}) {$ans->score(1) if $equal; return}
  104     $self->cmp_error($ans);
  105   } else {
  106     return if $ans->{ignoreStrings} && (!Value::isValue($student) || $student->type eq 'String');
  107     $ans->{ans_message} = $ans->{error_message} =
  108       "Your answer isn't ".lc($ans->{cmp_class}).
  109         " (it looks like ".lc($student->showClass).")"
  110      if !$ans->{isPreview} && $ans->{showTypeWarnings} && !$ans->{error_message};
  111   }
  112 }
  113 
  114 #
  115 #  Check if types are compatible for equality check
  116 #
  117 sub typeMatch {
  118   my $self = shift;  my $other = shift;
  119   return 1 unless ref($other);
  120   $self->type eq $other->type && $other->class ne 'Formula';
  121 }
  122 
  123 #
  124 #  Class name for cmp error messages
  125 #
  126 sub cmp_class {
  127   my $self = shift; my $ans = shift;
  128   my $class = $self->showClass; $class =~ s/Real //;
  129   return $class if $class =~ m/Formula/;
  130   return "an Interval or Union" if $class =~ m/Interval/i;
  131   return $class;
  132 }
  133 
  134 #
  135 #  Student answer evaluation failed.
  136 #  Report the error, with formatting, if possible.
  137 #
  138 sub cmp_error {
  139   my $self = shift; my $ans = shift;
  140   my $context = $$Value::context;
  141   my $message = $context->{error}{message};
  142   if ($context->{error}{pos}) {
  143     my $string = $context->{error}{string};
  144     my ($s,$e) = @{$context->{error}{pos}};
  145     $message =~ s/; see.*//;  # remove the position from the message
  146     $ans->{student_ans} =
  147        protectHTML(substr($string,0,$s)) .
  148        '<SPAN CLASS="parsehilight">' .
  149          protectHTML(substr($string,$s,$e-$s)) .
  150        '</SPAN>' .
  151        protectHTML(substr($string,$e));
  152   }
  153   $self->cmp_Error($ans,$message);
  154 }
  155 
  156 #
  157 #  Set the error message
  158 #
  159 sub cmp_Error {
  160   my $self = shift; my $ans = shift;
  161   return unless scalar(@_) > 0;
  162   $ans->score(0);
  163   $ans->{ans_message} = $ans->{error_message} = join("\n",@_);
  164 }
  165 
  166 #
  167 #  filled in by sub-classes
  168 #
  169 sub cmp_postprocess {}
  170 
  171 #
  172 #  Get and Set values in context
  173 #
  174 sub contextSet {
  175   my $context = shift; my %set = (@_);
  176   my $flags = $context->{flags}; my $get = {};
  177   foreach my $id (keys %set) {$get->{$id} = $flags->{$id}; $flags->{$id} = $set{$id}}
  178   return $get;
  179 }
  180 
  181 #
  182 #  Quote HTML characters
  183 #
  184 sub protectHTML {
  185     my $string = shift;
  186     return $string if eval ('$main::displayMode') eq 'TeX';
  187     $string =~ s/&/\&amp;/g;
  188     $string =~ s/</\&lt;/g;
  189     $string =~ s/>/\&gt;/g;
  190     $string;
  191 }
  192 
  193 #
  194 #  names for numbers
  195 #
  196 sub NameForNumber {
  197   my $self = shift; my $n = shift;
  198   my $name =  ('zeroth','first','second','third','fourth','fifth',
  199                'sixth','seventh','eighth','ninth','tenth')[$n];
  200   $name = "$n-th" if ($n > 10);
  201   return $name;
  202 }
  203 
  204 #
  205 #  Get a value from the safe compartment
  206 #
  207 sub getPG {
  208   my $self = shift;
  209 #  (WeBWorK::PG::Translator::PG_restricted_eval(shift))[0];
  210   eval ('package main; '.shift);  # faster
  211 }
  212 
  213 #############################################################
  214 #############################################################
  215 
  216 package Value::Real;
  217 
  218 sub cmp_defaults {(
  219   shift->SUPER::cmp_defaults,
  220   ignoreInfinity => 1,
  221 )}
  222 
  223 sub typeMatch {
  224   my $self = shift; my $other = shift; my $ans = shift;
  225   return 1 unless ref($other);
  226   return 0 if Value::isFormula($other);
  227   return 1 if $other->type eq 'Infinity' && $ans->{ignoreInfinity};
  228   $self->type eq $other->type;
  229 }
  230 
  231 #############################################################
  232 
  233 package Value::Infinity;
  234 
  235 sub cmp_class {'a Number'};
  236 
  237 sub typeMatch {
  238   my $self = shift; my $other = shift; my $ans = shift;
  239   return 1 unless ref($other);
  240   return 0 if Value::isFormula($other);
  241   return 1 if $other->type eq 'Number';
  242   $self->type eq $other->type;
  243 }
  244 
  245 #############################################################
  246 
  247 package Value::String;
  248 
  249 sub cmp_defaults {(
  250   Value::Real->cmp_defaults,
  251   typeMatch => 'Value::Real',
  252 )}
  253 
  254 sub cmp_class {
  255   my $self = shift; my $ans = shift; my $typeMatch = $ans->{typeMatch};
  256   return 'a Word' if !Value::isValue($typeMatch) || $typeMatch->class eq 'String';
  257   return $typeMatch->cmp_class;
  258 };
  259 
  260 sub typeMatch {
  261   my $self = shift; my $other = shift; my $ans = shift;
  262   return 0 if ref($other) && Value::isFormula($other);
  263   my $typeMatch = $ans->{typeMatch};
  264   return 1 if !Value::isValue($typeMatch) || $typeMatch->class eq 'String' ||
  265                  $self->type eq $other->type;
  266   return $typeMatch->typeMatch($other,$ans);
  267 }
  268 
  269 #############################################################
  270 
  271 package Value::Point;
  272 
  273 sub cmp_defaults {(
  274   shift->SUPER::cmp_defaults,
  275   showDimensionHints => 1,
  276   showCoordinateHints => 1,
  277 )}
  278 
  279 sub typeMatch {
  280   my $self = shift; my $other = shift; my $ans = shift;
  281   return ref($other) && $other->type eq 'Point' && $other->class ne 'Formula';
  282 }
  283 
  284 #
  285 #  Check for dimension mismatch and incorrect coordinates
  286 #
  287 sub cmp_postprocess {
  288   my $self = shift; my $ans = shift;
  289   return unless $ans->{score} == 0 && !$ans->{isPreview};
  290   if ($ans->{showDimensionHints} &&
  291       $self->length != $ans->{student_value}->length) {
  292     $self->cmp_Error($ans,"The dimension of your result is incorrect"); return;
  293   }
  294   if ($ans->{showCoordinateHints}) {
  295     my @errors;
  296     foreach my $i (1..$self->length) {
  297       push(@errors,"The ".$self->NameForNumber($i)." coordinate is incorrect")
  298   if ($self->{data}[$i-1] != $ans->{student_value}{data}[$i-1]);
  299     }
  300     $self->cmp_Error($ans,@errors); return;
  301   }
  302 }
  303 
  304 #############################################################
  305 
  306 package Value::Vector;
  307 
  308 sub cmp_defaults {(
  309   shift->SUPER::cmp_defaults,
  310   showDimensionHints => 1,
  311   showCoordinateHints => 1,
  312   promotePoints => 0,
  313   parallel => 0,
  314   sameDirection => 0,
  315 )}
  316 
  317 sub typeMatch {
  318   my $self = shift; my $other = shift; my $ans = shift;
  319   return 0 unless ref($other) && $other->class ne 'Formula';
  320   return $other->type eq 'Vector' ||
  321      ($ans->{promotePoints} && $other->type eq 'Point');
  322 }
  323 
  324 #
  325 #  check for dimension mismatch
  326 #        for parallel vectors, and
  327 #        for incorrect coordinates
  328 #
  329 sub cmp_postprocess {
  330   my $self = shift; my $ans = shift;
  331   return unless $ans->{score} == 0;
  332   if (!$ans->{isPreview} && $ans->{showDimensionHints} &&
  333       $self->length != $ans->{student_value}->length) {
  334     $self->cmp_Error($ans,"The dimension of your result is incorrect"); return;
  335   }
  336   if ($ans->{parallel} &&
  337       $self->isParallel($ans->{student_value},$ans->{sameDirection})) {
  338     $ans->score(1); return;
  339   }
  340   if (!$ans->{isPreview} && $ans->{showCoordinateHints} && !$ans->{parallel}) {
  341     my @errors;
  342     foreach my $i (1..$self->length) {
  343       push(@errors,"The ".$self->NameForNumber($i)." coordinate is incorrect")
  344   if ($self->{data}[$i-1] != $ans->{student_value}{data}[$i-1]);
  345     }
  346     $self->cmp_Error($ans,@errors); return;
  347   }
  348 }
  349 
  350 
  351 
  352 #############################################################
  353 
  354 package Value::Matrix;
  355 
  356 sub cmp_defaults {(
  357   shift->SUPER::cmp_defaults,
  358   showDimensionHints => 1,
  359   showEqualErrors => 0,
  360 )}
  361 
  362 sub typeMatch {
  363   my $self = shift; my $other = shift; my $ans = shift;
  364   return 0 unless ref($other) && $other->class ne 'Formula';
  365   return $other->type eq 'Matrix' ||
  366     ($other->type =~ m/^(Point|list)$/ &&
  367      $other->{open}.$other->{close} eq $self->{open}.$self->{close});
  368 }
  369 
  370 sub cmp_postprocess {
  371   my $self = shift; my $ans = shift;
  372   return unless $ans->{score} == 0 &&
  373     !$ans->{isPreview} && $ans->{showDimensionHints};
  374   my @d1 = $self->dimensions; my @d2 = $ans->{student_value}->dimensions;
  375   if (scalar(@d1) != scalar(@d2)) {
  376     $self->cmp_Error($ans,"Matrix dimension is not correct");
  377     return;
  378   } else {
  379     foreach my $i (0..scalar(@d1)-1) {
  380       if ($d1[$i] != $d2[$i]) {
  381   $self->cmp_Error($ans,"Matrix dimension is not correct");
  382   return;
  383       }
  384     }
  385   }
  386 }
  387 
  388 #############################################################
  389 
  390 package Value::Interval;
  391 
  392 sub cmp_defaults {(
  393   shift->SUPER::cmp_defaults,
  394   showEndpointHints => 1,
  395   showEndTypeHints => 1,
  396 )}
  397 
  398 sub typeMatch {
  399   my $self = shift; my $other = shift;
  400   return 0 unless ref($other) && $other->class ne 'Formula';
  401   return $other->length == 2 &&
  402          ($other->{open} eq '(' || $other->{open} eq '[') &&
  403          ($other->{close} eq ')' || $other->{close} eq ']')
  404      if $other->type =~ m/^(Point|List)$/;
  405   $other->type =~ m/^(Interval|Union)$/;
  406 }
  407 
  408 #
  409 #  Check for wrong enpoints and wrong type of endpoints
  410 #
  411 sub cmp_postprocess {
  412   my $self = shift; my $ans = shift;
  413   return unless $ans->{score} == 0 && !$ans->{isPreview};
  414   my $other = $ans->{student_value};
  415   return unless $other->class eq 'Interval';
  416   my @errors;
  417   if ($ans->{showEndpointHints}) {
  418     push(@errors,"Your left endpoint is incorrect")
  419       if ($self->{data}[0] != $other->{data}[0]);
  420     push(@errors,"Your right endpoint is incorrect")
  421       if ($self->{data}[1] != $other->{data}[1]);
  422   }
  423   if (scalar(@errors) == 0 && $ans->{showEndTypeHints}) {
  424     push(@errors,"The type of interval is incorrect")
  425       if ($self->{open}.$self->{close} ne $other->{open}.$other->{close});
  426   }
  427   $self->cmp_Error($ans,@errors);
  428 }
  429 
  430 #############################################################
  431 
  432 package Value::Union;
  433 
  434 sub typeMatch {
  435   my $self = shift; my $other = shift;
  436   return 0 unless ref($other) && $other->class ne 'Formula';
  437   return $other->length == 2 &&
  438          ($other->{open} eq '(' || $other->{open} eq '[') &&
  439          ($other->{close} eq ')' || $other->{close} eq ']')
  440      if $other->type =~ m/^(Point|List)$/;
  441   $other->type =~ m/^(Interval|Union)/;
  442 }
  443 
  444 #
  445 #  Use the List checker for unions, in order to get
  446 #  partial credit.  Set the various types for error
  447 #  messages.
  448 #
  449 sub cmp_defaults {(
  450   Value::List::cmp_defaults(@_),
  451   typeMatch => 'Value::Interval',
  452   list_type => 'an interval or union',
  453   entry_type => 'an interval',
  454 )}
  455 
  456 sub cmp_equal {Value::List::cmp_equal(@_)}
  457 
  458 #############################################################
  459 
  460 package Value::List;
  461 
  462 sub cmp_defaults {
  463   my $self = shift;
  464   return (
  465     Value::Real->cmp_defaults,
  466     showHints => undef,
  467     showLengthHints => undef,
  468     showParenHints => undef,
  469     partialCredit => undef,
  470     ordered => 0,
  471     entry_type => undef,
  472     list_type => undef,
  473     typeMatch => Value::makeValue($self->{data}[0]),
  474     requireParenMatch => 1,
  475     removeParens => 1,
  476    );
  477 }
  478 
  479 #
  480 #  Match anything but formulas
  481 #
  482 sub typeMatch {return !ref($other) || $other->class ne 'Formula'}
  483 
  484 #
  485 #  Handle removal of outermost parens in correct answer.
  486 #
  487 sub cmp {
  488   my $self = shift;
  489   my $cmp = $self->SUPER::cmp(@_);
  490   if ($cmp->{rh_ans}{removeParens}) {
  491     $self->{open} = $self->{close} = '';
  492     $cmp->ans_hash(correct_ans => $self->stringify);
  493   }
  494   return $cmp;
  495 }
  496 
  497 sub cmp_equal {
  498   my $self = shift; my $ans = shift;
  499   $ans->{showPartialCorrectAnswers} = $self->getPG('$showPartialCorrectAnswers');
  500 
  501   #
  502   #  get the paramaters
  503   #
  504   my $showTypeWarnings = $ans->{showTypeWarnings};
  505   my $showHints        = getOption($ans,'showHints');
  506   my $showLengthHints  = getOption($ans,'showLengthHints');
  507   my $showParenHints   = getOption($ans,'showLengthHints');
  508   my $partialCredit    = getOption($ans,'partialCredit');
  509   my $ordered = $ans->{ordered};
  510   my $requireParenMatch = $ans->{requireParenMatch};
  511   my $typeMatch = $ans->{typeMatch};
  512   my $value     = $ans->{entry_type};
  513   my $ltype     = $ans->{list_type} || lc($self->type);
  514 
  515   $value = (Value::isValue($typeMatch)? lc($typeMatch->cmp_class): 'value')
  516     unless defined($value);
  517   $value =~ s/(real|complex) //; $ans->{cmp_class} = $value;
  518   $value =~ s/^an? //; $value = 'formula' if $value =~ m/formula/;
  519   $ltype =~ s/^an? //;
  520   $showTypeWarnings = $showHints = $showLengthHints = 0 if $ans->{isPreview};
  521 
  522   #
  523   #  Get the lists of correct and student answers
  524   #   (split formulas that return lists or unions)
  525   #
  526   my @correct = (); my ($cOpen,$cClose);
  527   if ($self->class ne 'Formula') {
  528     @correct = $self->value;
  529     $cOpen = $ans->{correct_value}{open}; $cClose = $ans->{correct_value}{close};
  530   } else {
  531     @correct = Value::List->splitFormula($self,$ans);
  532     $cOpen = $self->{tree}{open}; $cClose = $self->{tree}{close};
  533   }
  534   my $student = $ans->{student_value}; my @student = ($student);
  535   my ($sOpen,$sClose) = ('','');
  536   if (Value::isFormula($student) && $student->type eq $self->type) {
  537     @student = Value::List->splitFormula($student,$ans);
  538     $sOpen = $student->{tree}{open}; $sClose = $student->{tree}{close};
  539   } elsif ($student->class ne 'Formula' && $student->class eq $self->type) {
  540     @student = @{$student->{data}};
  541     $sOpen = $student->{open}; $sClose = $student->{close};
  542   }
  543   return if $ans->{split_error};
  544   #
  545   #  Check for parenthesis match
  546   #
  547   if ($requireParenMatch && ($sOpen ne $cOpen || $sClose ne $cClose)) {
  548     if ($showParenHints && !($ans->{ignoreStrings} && $student->type eq 'String')) {
  549       my $message = "The parentheses for your $ltype ";
  550       if (($cOpen || $cClose) && ($sOpen || $sClose))
  551                                 {$message .= "are of the wrong type"}
  552       elsif ($sOpen || $sClose) {$message .= "should be removed"}
  553       else                      {$message .= "are missing"}
  554       $self->cmp_Error($ans,$message) unless $ans->{isPreview};
  555     }
  556     return;
  557   }
  558   #
  559   #  Check for empty lists
  560   #
  561   if (scalar(@correct) == 0 && scalar(@student) == 0) {$ans->score(1); return}
  562 
  563   #
  564   #  Initialize the score
  565   #
  566   my $M = scalar(@correct);
  567   my $m = scalar(@student);
  568   my $maxscore = ($m > $M)? $m : $M;
  569   my $score = 0; my @errors; my $i = 0;
  570 
  571   #
  572   #  Loop through student answers looking for correct ones
  573   #
  574   ENTRY: foreach my $entry (@student) {
  575     $i++;
  576     $entry = Value::makeValue($entry);
  577     $entry = Value::Formula->new($entry) if !Value::isValue($entry);
  578     if ($ordered) {
  579       if (eval {shift(@correct) == $entry}) {$score++; next ENTRY}
  580     } else {
  581       foreach my $k (0..$#correct) {
  582   if (eval {$correct[$k] == $entry}) {
  583     splice(@correct,$k,1);
  584     $score++; next ENTRY;
  585   }
  586       }
  587     }
  588     #
  589     #  Give messages about incorrect answers
  590     #
  591     my $nth = ''; my $answer = 'answer';
  592     my $class = $ans->{list_type} || $self->cmp_class;
  593     if (scalar(@student) > 1) {
  594       $nth = ' '.$self->NameForNumber($i);
  595       $class = $ans->{cmp_class};
  596       $answer = 'value';
  597     }
  598     if ($showTypeWarnings && !$typeMatch->typeMatch($entry,$ans) &&
  599   !($ans->{ignoreStrings} && $entry->class eq 'String')) {
  600       push(@errors,"Your$nth $answer isn't ".lc($class).
  601      " (it looks like ".lc($entry->showClass).")");
  602     } elsif ($showHints && $m > 1) {
  603       push(@errors,"Your$nth $value is incorrect");
  604     }
  605   }
  606 
  607   #
  608   #  Give hints about extra or missing answsers
  609   #
  610   if ($showLengthHints) {
  611     $value =~ s/ or /s or /; # fix "interval or union"
  612     push(@errors,"There should be more ${value}s in your $ltype")
  613       if ($score == $m && scalar(@correct) > 0);
  614     push(@errors,"There should be fewer ${value}s in your $ltype")
  615       if ($score < $maxscore && $score == $M && !$showHints);
  616   }
  617 
  618   #
  619   #  Finalize the score
  620   #
  621   $score = 0 if ($score != $maxscore && !$partialCredit);
  622   $ans->score($score/$maxscore);
  623   push(@errors,"Score = $ans->{score}") if $ans->{debug};
  624   $ans->{error_message} = $ans->{ans_message} = join("\n",@errors);
  625 }
  626 
  627 #
  628 #  Split a formula that is a list or union into a
  629 #    list of formulas (or Value objects).
  630 #
  631 sub splitFormula {
  632   my $self = shift; my $formula = shift; my $ans = shift;
  633   my @formula; my @entries;
  634   if ($formula->type eq 'List') {@entries = @{$formula->{tree}{coords}}}
  635       else {@entries = $formula->{tree}->makeUnion}
  636   foreach my $entry (@entries) {
  637     my $v = Parser::Formula($entry);
  638        $v = Parser::Evaluate($v) if (defined($v) && $v->isConstant);
  639     push(@formula,$v);
  640     #
  641     #  There shouldn't be an error evaluating the formula,
  642     #    but you never know...
  643     #
  644     if (!defined($v)) {$ans->{split_error} = 1; $self->cmp_error; return}
  645   }
  646   return @formula;
  647 }
  648 
  649 #
  650 #  Return the value if it is defined, otherwise use a default
  651 #
  652 sub getOption {
  653   my $ans = shift; my $name = shift;
  654   my $value = $ans->{$name};
  655   return $value if defined($value);
  656   return $ans->{showPartialCorrectAnswers};
  657 }
  658 
  659 #############################################################
  660 
  661 package Value::Formula;
  662 
  663 sub cmp_defaults {
  664   my $self = shift;
  665 
  666   return (
  667     Value::Union::cmp_defaults($self,@_),
  668     typeMatch => Value::Formula->new("(1,2]"),
  669   ) if $self->type eq 'Union';
  670 
  671   my $type = $self->type;
  672   $type = ($self->isComplex)? 'Complex': 'Real' if $type eq 'Number';
  673   $type = 'Value::'.$type.'::';
  674 
  675   return (&{$type.'cmp_defaults'}($self,@_), upToConstant => 0)
  676     if defined(%$type) && $self->type ne 'List';
  677 
  678   return (
  679     Value::List::cmp_defaults($self,@_),
  680     removeParens => $self->{autoFormula},
  681     typeMatch => Value::Formula->new(($self->createRandomPoints(1))[1]->[0]{data}[0]),
  682   );
  683 }
  684 
  685 #
  686 #  Get the types from the values of the formulas
  687 #     and compare those.
  688 #
  689 sub typeMatch {
  690   my $self = shift; my $other = shift; my $ans = shift;
  691   return 1 if $self->type eq $other->type;
  692   my $typeMatch = ($self->createRandomPoints(1))[1]->[0];
  693   $other = eval {($other->createRandomPoints(1))[1]->[0]} if Value::isFormula($other);
  694   return 1 unless defined($other); # can't really tell, so don't report type mismatch
  695   $typeMatch->typeMatch($other,$ans);
  696 }
  697 
  698 #
  699 #  Handle removal of outermost parens in a list.
  700 #
  701 sub cmp {
  702   my $self = shift;
  703   my $cmp = $self->SUPER::cmp(@_);
  704   if ($cmp->{rh_ans}{removeParens} && $self->type eq 'List') {
  705     $self->{tree}{open} = $self->{tree}{close} = '';
  706     $cmp->ans_hash(correct_ans => $self->stringify);
  707   }
  708   if ($cmp->{rh_ans}{eval} && $self->isConstant) {
  709     $cmp->ans_hash(correct_value => $self->eval);
  710     return $cmp;
  711   }
  712   if ($cmp->{rh_ans}{upToConstant}) {
  713     my $current = Parser::Context->current();
  714     my $context = $self->{context} = $self->{context}->copy;
  715     Parser::Context->current(undef,$context);
  716     $context->{_variables}->{pattern} = $context->{_variables}->{namePattern} =
  717       'C0|' . $context->{_variables}->{pattern};
  718     $context->update; $context->variables->add('C0' => 'Parameter');
  719     $cmp->ans_hash(correct_value => Value::Formula->new('C0')+$self);
  720     Parser::Context->current(undef,$current);
  721   }
  722   return $cmp;
  723 }
  724 
  725 sub cmp_equal {
  726   my $self = shift; my $ans = shift;
  727   #
  728   #  Get the problem's seed
  729   #
  730   $self->{context}->flags->set(
  731     random_seed => $self->getPG('$PG_original_problemSeed')
  732   );
  733 
  734   #
  735   #  Use the list checker if the formula is a list or union
  736   #    Otherwise use the normal checker
  737   #
  738   if ($self->type =~ m/^(List|Union)$/) {
  739     Value::List::cmp_equal($self,$ans);
  740   } else {
  741     $self->SUPER::cmp_equal($ans);
  742   }
  743 }
  744 
  745 sub cmp_postprocess {
  746   my $self = shift; my $ans = shift;
  747   return unless $ans->{score} == 0 && !$ans->{isPreview};
  748   return if $ans->{ans_message} || !$ans->{showDimensionHints};
  749   my $other = $ans->{student_value};
  750   return unless $other->type =~ m/^(Point|Vector|Matrix)$/;
  751   return unless $self->type  =~ m/^(Point|Vector|Matrix)$/;
  752   return if Parser::Item::typeMatch($self->typeRef,$other->typeRef);
  753   $self->cmp_Error($ans,"The dimension of your result is incorrect");
  754 }
  755 
  756 #############################################################
  757 
  758 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9