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