[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 2791 - (download) (as text) (annotate)
Thu Sep 16 23:43:54 2004 UTC (8 years, 8 months ago) by dpvc
File size: 22346 byte(s)
Don't reduce constants in the display of student answers (so they can
tell how the parser interprets their answer).  The drawback is that
the final number is not displayed.  (We may need to add more columns
to the results display.)

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9