Parent Directory
|
Revision Log
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/&/\&/g; 188 $string =~ s/</\</g; 189 $string =~ s/>/\>/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 |