| … | |
… | |
| 23 | ignoreStrings => 1, |
23 | ignoreStrings => 1, |
| 24 | )} |
24 | )} |
| 25 | |
25 | |
| 26 | sub cmp { |
26 | sub cmp { |
| 27 | my $self = shift; |
27 | my $self = shift; |
| 28 | $$Value::context->flags->set(StringifyAsTeX => 0); # reset this, just in case. |
|
|
| 29 | my $ans = new AnswerEvaluator; |
28 | my $ans = new AnswerEvaluator; |
| 30 | $ans->ans_hash( |
29 | $ans->ans_hash( |
| 31 | type => "Value (".$self->class.")", |
30 | type => "Value (".$self->class.")", |
| 32 | correct_ans => $self->string, |
31 | correct_ans => $self->string, |
| 33 | correct_value => $self, |
32 | correct_value => $self, |
| 34 | $self->cmp_defaults, |
33 | $self->cmp_defaults, |
| 35 | @_ |
34 | @_ |
| 36 | ); |
35 | ); |
| 37 | $ans->install_evaluator( |
36 | $ans->install_evaluator(sub {$ans = shift; $ans->{correct_value}->cmp_parse($ans)}); |
| 38 | sub { |
37 | $self->{context} = $$Value::context unless defined($self->{context}); |
| 39 | my $ans = shift; |
|
|
| 40 | # can't seem to get $inputs_ref any other way |
|
|
| 41 | $ans->{isPreview} = $self->getPG('$inputs_ref->{previewAnswers}'); |
|
|
| 42 | my $self = $ans->{correct_value}; |
|
|
| 43 | my $method = $ans->{cmp_check} || 'cmp_check'; |
|
|
| 44 | $ans->{cmp_class} = $self->cmp_class($ans) unless $ans->{cmp_class}; |
|
|
| 45 | $self->$method($ans); |
|
|
| 46 | } |
|
|
| 47 | ); |
|
|
| 48 | return $ans; |
38 | return $ans; |
| 49 | } |
39 | } |
| 50 | |
40 | |
| 51 | # |
41 | # |
| 52 | # Parse the student answer and compute its value, |
42 | # Parse the student answer and compute its value, |
| 53 | # produce the preview strings, and then compare the |
43 | # produce the preview strings, and then compare the |
| 54 | # student and professor's answers for equality. |
44 | # student and professor's answers for equality. |
| 55 | # |
45 | # |
| 56 | sub cmp_check { |
46 | sub cmp_parse { |
| 57 | my $self = shift; my $ans = shift; |
47 | my $self = shift; my $ans = shift; |
| 58 | # |
48 | # |
| 59 | # Methods to call |
49 | # Do some setup |
| 60 | # |
50 | # |
| 61 | my $cmp_equal = $ans->{cmp_equal} || 'cmp_equal'; |
51 | my $context = $$Value::context; # save it for later |
| 62 | my $cmp_error = $ans->{cmp_error} || 'cmp_error'; |
52 | Parser::Context->current(undef,$self->{context}); # change to object's context |
| 63 | my $cmp_postprocess = $ans->{cmp_postprocess} || 'cmp_postprocess'; |
53 | $context->flags->set(StringifyAsTeX => 0); # reset this, just in case. |
|
|
54 | $ans->{isPreview} = $self->getPG('$inputs_ref->{previewAnswers}'); |
|
|
55 | $ans->{cmp_class} = $self->cmp_class($ans) unless $ans->{cmp_class}; |
|
|
56 | |
| 64 | # |
57 | # |
| 65 | # Parse and evaluate the student answer |
58 | # Parse and evaluate the student answer |
| 66 | # |
59 | # |
| 67 | $ans->score(0); # assume failure |
60 | $ans->score(0); # assume failure |
| 68 | $ans->{student_value} = $ans->{student_formula} = Parser::Formula($ans->{student_ans}); |
61 | $ans->{student_value} = $ans->{student_formula} = Parser::Formula($ans->{student_ans}); |
| 69 | $ans->{student_value} = Parser::Evaluate($ans->{student_formula}) |
62 | $ans->{student_value} = Parser::Evaluate($ans->{student_formula}) |
| 70 | if defined($ans->{student_formula}) && $ans->{student_formula}->isConstant; |
63 | if defined($ans->{student_formula}) && $ans->{student_formula}->isConstant; |
|
|
64 | |
| 71 | # |
65 | # |
| 72 | # If it parsed OK, save the output forms and check if it is correct |
66 | # If it parsed OK, save the output forms and check if it is correct |
| 73 | # otherwise report an error |
67 | # otherwise report an error |
| 74 | # |
68 | # |
| 75 | if (defined $ans->{student_value}) { |
69 | if (defined $ans->{student_value}) { |
| 76 | $ans->{student_value} = Value::Formula->new($ans->{student_value}) |
70 | $ans->{student_value} = Value::Formula->new($ans->{student_value}) |
| 77 | unless Value::isValue($ans->{student_value}); |
71 | unless Value::isValue($ans->{student_value}); |
| 78 | $ans->{preview_latex_string} = $ans->{student_formula}->TeX; |
72 | $ans->{preview_latex_string} = $ans->{student_formula}->TeX; |
| 79 | $ans->{preview_text_string} = $ans->{student_formula}->string; |
73 | $ans->{preview_text_string} = protectHTML($ans->{student_formula}->string); |
| 80 | $ans->{student_ans} = $ans->{preview_text_string}; |
74 | $ans->{student_ans} = $ans->{preview_text_string}; |
| 81 | $self->$cmp_equal($ans); |
75 | $self->cmp_equal($ans); |
| 82 | $self->$cmp_postprocess($ans) if !$ans->{error_message}; |
76 | $self->cmp_postprocess($ans) if !$ans->{error_message}; |
| 83 | } else { |
77 | } else { |
| 84 | $self->$cmp_error($ans); |
78 | $self->cmp_error($ans); |
| 85 | } |
79 | } |
|
|
80 | Parser::Context->current(undef,$context); # put back the old context |
| 86 | return $ans; |
81 | return $ans; |
| 87 | } |
82 | } |
| 88 | |
83 | |
| 89 | # |
84 | # |
| 90 | # Check if the parsed student answer equals the professor's answer |
85 | # Check if the parsed student answer equals the professor's answer |
| … | |
… | |
| 94 | my $correct = $ans->{correct_value}; |
89 | my $correct = $ans->{correct_value}; |
| 95 | my $student = $ans->{student_value}; |
90 | my $student = $ans->{student_value}; |
| 96 | if ($correct->typeMatch($student,$ans)) { |
91 | if ($correct->typeMatch($student,$ans)) { |
| 97 | my $equal = eval {$correct == $student}; |
92 | my $equal = eval {$correct == $student}; |
| 98 | if (defined($equal) || !$ans->{showEqualErrors}) {$ans->score(1) if $equal; return} |
93 | if (defined($equal) || !$ans->{showEqualErrors}) {$ans->score(1) if $equal; return} |
| 99 | my $cmp_error = $ans->{cmp_error} || 'cmp_error'; |
|
|
| 100 | $self->$cmp_error($ans); |
94 | $self->cmp_error($ans); |
| 101 | } else { |
95 | } else { |
| 102 | return if $ans->{ignoreStrings} && (!Value::isValue($student) || $student->type eq 'String'); |
96 | return if $ans->{ignoreStrings} && (!Value::isValue($student) || $student->type eq 'String'); |
| 103 | $ans->{ans_message} = $ans->{error_message} = |
97 | $ans->{ans_message} = $ans->{error_message} = |
| 104 | "Your answer isn't ".lc($ans->{cmp_class}). |
98 | "Your answer isn't ".lc($ans->{cmp_class}). |
| 105 | " (it looks like ".lc($student->showClass).")" |
99 | " (it looks like ".lc($student->showClass).")" |
| … | |
… | |
| 205 | )} |
199 | )} |
| 206 | |
200 | |
| 207 | sub typeMatch { |
201 | sub typeMatch { |
| 208 | my $self = shift; my $other = shift; my $ans = shift; |
202 | my $self = shift; my $other = shift; my $ans = shift; |
| 209 | return 1 unless ref($other); |
203 | return 1 unless ref($other); |
| 210 | return 0 if $other->class eq 'Formula'; |
204 | return 0 if Value::isFormula($other); |
| 211 | return 1 if $other->type eq 'Infinity' && $ans->{ignoreInfinity}; |
205 | return 1 if $other->type eq 'Infinity' && $ans->{ignoreInfinity}; |
| 212 | $self->type eq $other->type; |
206 | $self->type eq $other->type; |
| 213 | } |
207 | } |
| 214 | |
208 | |
| 215 | ############################################################# |
209 | ############################################################# |
| … | |
… | |
| 219 | sub cmp_class {'a Number'}; |
213 | sub cmp_class {'a Number'}; |
| 220 | |
214 | |
| 221 | sub typeMatch { |
215 | sub typeMatch { |
| 222 | my $self = shift; my $other = shift; my $ans = shift; |
216 | my $self = shift; my $other = shift; my $ans = shift; |
| 223 | return 1 unless ref($other); |
217 | return 1 unless ref($other); |
| 224 | return 0 if $other->class eq 'Formula'; |
218 | return 0 if Value::isFormula($other); |
| 225 | return 1 if $other->type eq 'Number'; |
219 | return 1 if $other->type eq 'Number'; |
| 226 | $self->type eq $other->type; |
220 | $self->type eq $other->type; |
| 227 | } |
221 | } |
| 228 | |
222 | |
| 229 | ############################################################# |
223 | ############################################################# |
| … | |
… | |
| 241 | return $typeMatch->cmp_class; |
235 | return $typeMatch->cmp_class; |
| 242 | }; |
236 | }; |
| 243 | |
237 | |
| 244 | sub typeMatch { |
238 | sub typeMatch { |
| 245 | my $self = shift; my $other = shift; my $ans = shift; |
239 | my $self = shift; my $other = shift; my $ans = shift; |
| 246 | return 0 if ref($other) && $other->class eq 'Formula'; |
240 | return 0 if ref($other) && Value::isFormula($other); |
| 247 | my $typeMatch = $ans->{typeMatch}; |
241 | my $typeMatch = $ans->{typeMatch}; |
| 248 | return 1 if !Value::isValue($typeMatch) || $typeMatch->class eq 'String' || |
242 | return 1 if !Value::isValue($typeMatch) || $typeMatch->class eq 'String' || |
| 249 | $self->type eq $other->type; |
243 | $self->type eq $other->type; |
| 250 | return $typeMatch->typeMatch($other,$ans); |
244 | return $typeMatch->typeMatch($other,$ans); |
| 251 | } |
245 | } |
| … | |
… | |
| 508 | my @correct = (); |
502 | my @correct = (); |
| 509 | if ($self->class ne 'Formula') {@correct = $self->value} |
503 | if ($self->class ne 'Formula') {@correct = $self->value} |
| 510 | else {@correct = Value::List->splitFormula($self,$ans)} |
504 | else {@correct = Value::List->splitFormula($self,$ans)} |
| 511 | my $student = $ans->{student_value}; |
505 | my $student = $ans->{student_value}; |
| 512 | my @student = ($student); |
506 | my @student = ($student); |
| 513 | if ($student->class eq 'Formula' && $student->type eq $self->type) { |
507 | if (Value::isFormula($student) && $student->type eq $self->type) { |
| 514 | @student = Value::List->splitFormula($student,$ans); |
508 | @student = Value::List->splitFormula($student,$ans); |
| 515 | } elsif ($student->class ne 'Formula' && $student->class eq $self->type && |
509 | } elsif ($student->class ne 'Formula' && $student->class eq $self->type && |
| 516 | ($allowParens || (!$student->{open} && !$student->{close}))) { |
510 | ($allowParens || (!$student->{open} && !$student->{close}))) { |
| 517 | @student = @{$student->{data}}; |
511 | @student = @{$student->{data}}; |
| 518 | } |
512 | } |
| … | |
… | |
| 598 | push(@formula,$v); |
592 | push(@formula,$v); |
| 599 | # |
593 | # |
| 600 | # There shouldn't be an error evaluating the formula, |
594 | # There shouldn't be an error evaluating the formula, |
| 601 | # but you never know... |
595 | # but you never know... |
| 602 | # |
596 | # |
| 603 | if (!defined($v)) { |
597 | if (!defined($v)) {$ans->{split_error} = 1; $self->cmp_error; return} |
| 604 | $ans->{split_error} = 1; |
|
|
| 605 | my $cmp_error = $ans->{cmp_error} || 'cmp_error'; |
|
|
| 606 | $self->$cmp_error; return; |
|
|
| 607 | } |
|
|
| 608 | } |
598 | } |
| 609 | return @formula; |
599 | return @formula; |
| 610 | } |
600 | } |
| 611 | |
601 | |
| 612 | # |
602 | # |
| … | |
… | |
| 645 | # |
635 | # |
| 646 | sub typeMatch { |
636 | sub typeMatch { |
| 647 | my $self = shift; my $other = shift; my $ans = shift; |
637 | my $self = shift; my $other = shift; my $ans = shift; |
| 648 | return 1 if $self->type eq $other->type; |
638 | return 1 if $self->type eq $other->type; |
| 649 | my $typeMatch = ($self->createRandomPoints(1))[1]->[0]; |
639 | my $typeMatch = ($self->createRandomPoints(1))[1]->[0]; |
| 650 | $other = eval {($other->createRandomPoints(1))[1]->[0]} if ($other->class eq 'Formula'); |
640 | $other = eval {($other->createRandomPoints(1))[1]->[0]} if Value::isFormula($other); |
| 651 | return 1 unless defined($other); # can't really tell, so don't report type mismatch |
641 | return 1 unless defined($other); # can't really tell, so don't report type mismatch |
| 652 | $typeMatch->typeMatch($other,$ans); |
642 | $typeMatch->typeMatch($other,$ans); |
| 653 | } |
643 | } |
| 654 | |
644 | |
| 655 | # |
645 | # |