| … | |
… | |
| 96 | if (defined($equal) || !$ans->{showEqualErrors}) {$ans->score(1) if $equal; return} |
96 | if (defined($equal) || !$ans->{showEqualErrors}) {$ans->score(1) if $equal; return} |
| 97 | my $cmp_error = $ans->{cmp_error} || 'cmp_error'; |
97 | my $cmp_error = $ans->{cmp_error} || 'cmp_error'; |
| 98 | $self->$cmp_error($ans); |
98 | $self->$cmp_error($ans); |
| 99 | } else { |
99 | } else { |
| 100 | $ans->{ans_message} = $ans->{error_message} = |
100 | $ans->{ans_message} = $ans->{error_message} = |
| 101 | "Your answer isn't ".lc($ans->{correct_value}->showClass). |
101 | "Your answer isn't ".lc($ans->{correct_value}->showCmpClass). |
| 102 | " (it looks like ".lc($ans->{student_value}->showClass).")" |
102 | " (it looks like ".lc($ans->{student_value}->showClass).")" |
| 103 | if !$ans->{isPreview} && $ans->{showTypeWarnings} && !$ans->{error_message}; |
103 | if !$ans->{isPreview} && $ans->{showTypeWarnings} && !$ans->{error_message}; |
| 104 | } |
104 | } |
| 105 | } |
105 | } |
| 106 | |
106 | |
| … | |
… | |
| 110 | sub typeMatch { |
110 | sub typeMatch { |
| 111 | my $self = shift; my $other = shift; |
111 | my $self = shift; my $other = shift; |
| 112 | return 1 unless ref($other); |
112 | return 1 unless ref($other); |
| 113 | $self->type eq $other->type; |
113 | $self->type eq $other->type; |
| 114 | } |
114 | } |
|
|
115 | |
|
|
116 | # |
|
|
117 | # Class name for cmp error messages |
|
|
118 | # |
|
|
119 | sub showCmpClass {shift->showClass} |
| 115 | |
120 | |
| 116 | # |
121 | # |
| 117 | # Student answer evaluation failed. |
122 | # Student answer evaluation failed. |
| 118 | # Report the error, with formatting, if possible. |
123 | # Report the error, with formatting, if possible. |
| 119 | # |
124 | # |
| … | |
… | |
| 186 | package Value::Real; |
191 | package Value::Real; |
| 187 | |
192 | |
| 188 | our $cmp_defaults = { |
193 | our $cmp_defaults = { |
| 189 | %{$Value::cmp_defaults}, |
194 | %{$Value::cmp_defaults}, |
| 190 | ignoreStrings => 1, |
195 | ignoreStrings => 1, |
|
|
196 | ignoreInfinity => 1, |
| 191 | }; |
197 | }; |
| 192 | |
198 | |
| 193 | sub typeMatch { |
199 | sub typeMatch { |
| 194 | my $self = shift; my $other = shift; my $ans = shift; |
200 | my $self = shift; my $other = shift; my $ans = shift; |
| 195 | return 1 unless ref($other); |
201 | return 1 unless ref($other); |
|
|
202 | return 1 if $other->type eq 'Infinity' && $ans->{ignoreInfinity}; |
| 196 | if ($other->type eq 'String' && $ans->{ignoreStrings}) { |
203 | if ($other->type eq 'String' && $ans->{ignoreStrings}) { |
| 197 | $ans->{showEqualErrors} = 0; |
204 | $ans->{showEqualErrors} = 0; |
| 198 | return 1; |
205 | return 1; |
| 199 | } |
206 | } |
|
|
207 | $self->type eq $other->type; |
|
|
208 | } |
|
|
209 | |
|
|
210 | ############################################################# |
|
|
211 | |
|
|
212 | package Value::Infinity; |
|
|
213 | |
|
|
214 | sub showCmpClass {'a Number'} |
|
|
215 | |
|
|
216 | sub typeMatch { |
|
|
217 | my $self = shift; my $other = shift; my $ans = shift; |
|
|
218 | return 1 unless ref($other); |
|
|
219 | return 1 if $other->type eq 'Number'; |
| 200 | $self->type eq $other->type; |
220 | $self->type eq $other->type; |
| 201 | } |
221 | } |
| 202 | |
222 | |
| 203 | ############################################################# |
223 | ############################################################# |
| 204 | |
224 | |
| … | |
… | |
| 328 | %{$Value::cmp_defaults}, |
348 | %{$Value::cmp_defaults}, |
| 329 | showEndpointHints => 1, |
349 | showEndpointHints => 1, |
| 330 | showEndTypeHints => 1, |
350 | showEndTypeHints => 1, |
| 331 | }; |
351 | }; |
| 332 | |
352 | |
|
|
353 | sub showCmpClass {'an Interval or Union'} |
|
|
354 | |
| 333 | sub typeMatch { |
355 | sub typeMatch { |
| 334 | my $self = shift; my $other = shift; |
356 | my $self = shift; my $other = shift; |
| 335 | return 0 unless ref($other); |
357 | return 0 unless ref($other); |
| 336 | return $other->length == 2 && |
358 | return $other->length == 2 && |
| 337 | ($other->{open} eq '(' || $other->{open} eq '[') && |
359 | ($other->{open} eq '(' || $other->{open} eq '[') && |
| … | |
… | |
| 364 | |
386 | |
| 365 | ############################################################# |
387 | ############################################################# |
| 366 | |
388 | |
| 367 | package Value::Union; |
389 | package Value::Union; |
| 368 | |
390 | |
|
|
391 | sub showCmpClass {'an Interval or Union'} |
|
|
392 | |
| 369 | sub typeMatch { |
393 | sub typeMatch { |
| 370 | my $self = shift; my $other = shift; |
394 | my $self = shift; my $other = shift; |
| 371 | return 0 unless ref($other); |
395 | return 0 unless ref($other); |
| 372 | return $other->length == 2 && |
396 | return $other->length == 2 && |
| 373 | ($other->{open} eq '(' || $other->{open} eq '[') && |
397 | ($other->{open} eq '(' || $other->{open} eq '[') && |
| … | |
… | |
| 379 | ############################################################# |
403 | ############################################################# |
| 380 | |
404 | |
| 381 | package Value::List; |
405 | package Value::List; |
| 382 | |
406 | |
| 383 | our $cmp_defaults = { |
407 | our $cmp_defaults = { |
| 384 | %{$Value::cmp_defaults}, |
408 | %{$Value::Real::cmp_defaults}, |
| 385 | showHints => undef, |
409 | showHints => undef, |
| 386 | showLengthHints => undef, |
410 | showLengthHints => undef, |
| 387 | # partialCredit => undef, |
411 | # partialCredit => undef, |
| 388 | partialCredit => 0, # only allow this once WW can deal with partial credit |
412 | partialCredit => 0, # only allow this once WW can deal with partial credit |
| 389 | ordered => 0, |
413 | ordered => 0, |
| … | |
… | |
| 439 | $maxscore = $m if ($m > $maxscore); |
463 | $maxscore = $m if ($m > $maxscore); |
| 440 | my $score = 0; my @errors; my $i = 0; |
464 | my $score = 0; my @errors; my $i = 0; |
| 441 | |
465 | |
| 442 | ENTRY: foreach my $entry (@student) { |
466 | ENTRY: foreach my $entry (@student) { |
| 443 | $i++; |
467 | $i++; |
| 444 | $entry = Value::Real->make($entry) if !ref($entry) && Value::matchNumber($entry); |
468 | $entry = Value::makeValue($entry); |
| 445 | $entry = Value::Formula->new($entry) if !Value::isValue($entry); |
469 | $entry = Value::Formula->new($entry) if !Value::isValue($entry); |
| 446 | if ($ordered) { |
470 | if ($ordered) { |
| 447 | if (eval {shift(@correct) == $entry}) {$score++; next ENTRY} |
471 | if (eval {shift(@correct) == $entry}) {$score++; next ENTRY} |
| 448 | } else { |
472 | } else { |
| 449 | foreach my $k (0..$#correct) { |
473 | foreach my $k (0..$#correct) { |
| … | |
… | |
| 454 | } |
478 | } |
| 455 | } |
479 | } |
| 456 | if ($showTypeWarnings && defined($typeMatch) && |
480 | if ($showTypeWarnings && defined($typeMatch) && |
| 457 | !$typeMatch->typeMatch($entry,$ans)) { |
481 | !$typeMatch->typeMatch($entry,$ans)) { |
| 458 | push(@errors, |
482 | push(@errors, |
| 459 | "Your ".$self->NameForNumber($i)." value isn't ".lc($typeMatch->showClass). |
483 | "Your ".$self->NameForNumber($i)." value isn't ".lc($typeMatch->showCmpClass). |
| 460 | " (it looks like ".lc($entry->showClass).")"); |
484 | " (it looks like ".lc($entry->showClass).")"); |
| 461 | next ENTRY; |
485 | next ENTRY; |
| 462 | } |
486 | } |
| 463 | push(@errors,"Your ".$self->NameForNumber($i)." $value is incorrect") |
487 | push(@errors,"Your ".$self->NameForNumber($i)." $value is incorrect") |
| 464 | if $showHints && $m > 1; |
488 | if $showHints && $m > 1; |