From 0cf8cd437ad65f3d19da093ce6b30b2938bdac98 Mon Sep 17 00:00:00 2001 From: Nathan Wallach Date: Tue, 24 May 2022 21:28:08 +0300 Subject: [PATCH 1/4] Patch AnswerHints so that if a subroutine in the list "dies" there will still be a final result from the AnswerHints postfilter. --- macros/answerHints.pl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/macros/answerHints.pl b/macros/answerHints.pl index 6c67574fb5..86843e0e11 100644 --- a/macros/answerHints.pl +++ b/macros/answerHints.pl @@ -146,11 +146,16 @@ sub AnswerHints { foreach my $wrong (@{$wrongList}) { if (ref($wrong) eq 'CODE') { if (($ans->{score} < 1 || $options{checkCorrect}) && - ($ans->{ans_message} eq "" || $options{replaceMessage}) && - &$wrong($correct,$student,$ans)) { - $ans->{ans_message} = $ans->{error_message} = $message; - $ans->{score} = $options{score} if defined $options{score}; - last; + ($ans->{ans_message} eq "" || $options{replaceMessage}) ) { + # Make the call to run the function inside an eval to trap errors + my $myResult = 0; + eval { $myResult = &$wrong($correct,$student,$ans); }; + if ( $@ ) { $myResult = 0; } + if ( $myResult ) { + $ans->{ans_message} = $ans->{error_message} = $message; + $ans->{score} = $options{score} if defined $options{score}; + last; + } } } else { $wrong = Value::makeValue($wrong); From ddcc42c04453b4cdaa6d670d4d053189fcd7dbb4 Mon Sep 17 00:00:00 2001 From: Nathan Wallach Date: Thu, 26 May 2022 16:06:46 +0300 Subject: [PATCH 2/4] Provide an error message as the result message when a subroutine called by AnswerHints dies. --- macros/answerHints.pl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/macros/answerHints.pl b/macros/answerHints.pl index 86843e0e11..725c92b8c1 100644 --- a/macros/answerHints.pl +++ b/macros/answerHints.pl @@ -149,9 +149,11 @@ sub AnswerHints { ($ans->{ans_message} eq "" || $options{replaceMessage}) ) { # Make the call to run the function inside an eval to trap errors my $myResult = 0; - eval { $myResult = &$wrong($correct,$student,$ans); }; - if ( $@ ) { $myResult = 0; } - if ( $myResult ) { + eval { $myResult = &$wrong($correct,$student,$ans); } or do { + $ans->{ans_message} = "error during AnswerHints processing"; + last; + }; + if ($myResult) { $ans->{ans_message} = $ans->{error_message} = $message; $ans->{score} = $options{score} if defined $options{score}; last; From daec8d93352eea39df4bc135b3ce19f457766c60 Mon Sep 17 00:00:00 2001 From: Nathan Wallach Date: Thu, 26 May 2022 16:12:19 +0300 Subject: [PATCH 3/4] perltidy macros/answerHints.pl --- macros/answerHints.pl | 169 +++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 75 deletions(-) diff --git a/macros/answerHints.pl b/macros/answerHints.pl index 725c92b8c1..f88f64a890 100644 --- a/macros/answerHints.pl +++ b/macros/answerHints.pl @@ -113,66 +113,81 @@ =head1 AnswerHints() =cut -sub _answerHints_init {} +sub _answerHints_init { } sub AnswerHints { - return (sub { - my $ans = shift; $ans->{_filter_name} = "Answer Hints Post Filter"; - my $correct = $ans->{correct_value}; - my $student = $ans->{student_value}; - Value::Error("AnswerHints can only be used with MathObjects answer checkers") unless ref($correct); - return $ans unless ref($student); - my $context = $correct->context; - my $hash = $context->{answerHash}; - $context->{answerHash} = $ans; - my $processPreview = $correct->getFlag('answerHintsProcessPreview', 0); - $context->{answerHash} = $hash; - - while (@_) { - my $wrongList = shift; my $message = shift; my @options; - ($message,@options) = @{$message} if ref($message) eq 'ARRAY'; - my %options = ( - checkCorrect => 0, - replaceMessage => 0, - checkTypes => 1, - processPreview => $processPreview, - score => undef, - cmp_options => [], - @options, - ); - next if $options{checkTypes} && $correct->type ne $student->type; - next if !$options{processPreview} && $ans->{isPreview}; - $wrongList = [$wrongList] unless ref($wrongList) eq 'ARRAY'; - foreach my $wrong (@{$wrongList}) { - if (ref($wrong) eq 'CODE') { - if (($ans->{score} < 1 || $options{checkCorrect}) && - ($ans->{ans_message} eq "" || $options{replaceMessage}) ) { - # Make the call to run the function inside an eval to trap errors - my $myResult = 0; - eval { $myResult = &$wrong($correct,$student,$ans); } or do { - $ans->{ans_message} = "error during AnswerHints processing"; - last; - }; - if ($myResult) { - $ans->{ans_message} = $ans->{error_message} = $message; - $ans->{score} = $options{score} if defined $options{score}; - last; - } - } - } else { - $wrong = Value::makeValue($wrong); - if (($ans->{score} < 1 || $options{checkCorrect} || AnswerHints::Compare($correct,$wrong,$ans)) && - ($ans->{ans_message} eq "" || $options{replaceMessage}) && - AnswerHints::Compare($wrong,$student,$ans,@{$options{cmp_options}})) { - $ans->{ans_message} = $ans->{error_message} = $message; - $ans->{score} = $options{score} if defined $options{score}; - last; - } - } - } - } - return $ans; - },@_); + return ( + sub { + my $ans = shift; + $ans->{_filter_name} = "Answer Hints Post Filter"; + my $correct = $ans->{correct_value}; + my $student = $ans->{student_value}; + Value::Error("AnswerHints can only be used with MathObjects answer checkers") unless ref($correct); + return $ans unless ref($student); + my $context = $correct->context; + my $hash = $context->{answerHash}; + $context->{answerHash} = $ans; + my $processPreview = $correct->getFlag('answerHintsProcessPreview', 0); + $context->{answerHash} = $hash; + + while (@_) { + my $wrongList = shift; + my $message = shift; + my @options; + ($message, @options) = @{$message} if ref($message) eq 'ARRAY'; + my %options = ( + checkCorrect => 0, + replaceMessage => 0, + checkTypes => 1, + processPreview => $processPreview, + score => undef, + cmp_options => [], + @options, + ); + next if $options{checkTypes} && $correct->type ne $student->type; + next if !$options{processPreview} && $ans->{isPreview}; + $wrongList = [$wrongList] unless ref($wrongList) eq 'ARRAY'; + + foreach my $wrong (@{$wrongList}) { + if (ref($wrong) eq 'CODE') { + if ( ($ans->{score} < 1 || $options{checkCorrect}) + && ($ans->{ans_message} eq "" || $options{replaceMessage})) + { + # Make the call to run the function inside an eval to trap errors + my $myResult = 0; + eval { $myResult = &$wrong($correct, $student, $ans); } or do { + $ans->{ans_message} = "error during AnswerHints processing"; + last; + }; + if ($myResult) { + $ans->{ans_message} = $ans->{error_message} = $message; + $ans->{score} = $options{score} if defined $options{score}; + last; + } + } + } else { + $wrong = Value::makeValue($wrong); + if ( + ( + $ans->{score} < 1 + || $options{checkCorrect} + || AnswerHints::Compare($correct, $wrong, $ans) + ) + && ($ans->{ans_message} eq "" || $options{replaceMessage}) + && AnswerHints::Compare($wrong, $student, $ans, @{ $options{cmp_options} }) + ) + { + $ans->{ans_message} = $ans->{error_message} = $message; + $ans->{score} = $options{score} if defined $options{score}; + last; + } + } + } + } + return $ans; + }, + @_ + ); } package AnswerHints; @@ -182,23 +197,27 @@ package AnswerHints; # and returns true if the two values match and false otherwise. # sub Compare { - my $self = shift; my $other = shift; my $ans = shift; - $ans = bless {%{$ans},@_}, ref($ans); # make a copy - $ans->{typeError} = 0; $ans->{ans_message} = $ans->{error_message} = ""; $ans->{score} = 0; - if (sprintf("%p",$self) ne sprintf("%p",$ans->{correct_value})) { - $ans->{correct_ans} = $self->string; - $ans->{correct_value} = $self; - $ans->{correct_formula} = Value->Package("Formula")->new($self); - } - if (sprintf("%p",$other) ne sprintf("%p",$ans->{student_value})) { - $ans->{student_ans} = $other->string; - $ans->{student_value} = $other; - $ans->{student_formula} = Value->Package("Formula")->new($other); - } - $self->cmp_preprocess($ans); - $self->cmp_equal($ans); - $self->cmp_postprocess($ans) if !$ans->{error_message} && !$ans->{typeError}; - return $ans->{score} >= 1; + my $self = shift; + my $other = shift; + my $ans = shift; + $ans = bless { %{$ans}, @_ }, ref($ans); # make a copy + $ans->{typeError} = 0; + $ans->{ans_message} = $ans->{error_message} = ""; + $ans->{score} = 0; + if (sprintf("%p", $self) ne sprintf("%p", $ans->{correct_value})) { + $ans->{correct_ans} = $self->string; + $ans->{correct_value} = $self; + $ans->{correct_formula} = Value->Package("Formula")->new($self); + } + if (sprintf("%p", $other) ne sprintf("%p", $ans->{student_value})) { + $ans->{student_ans} = $other->string; + $ans->{student_value} = $other; + $ans->{student_formula} = Value->Package("Formula")->new($other); + } + $self->cmp_preprocess($ans); + $self->cmp_equal($ans); + $self->cmp_postprocess($ans) if !$ans->{error_message} && !$ans->{typeError}; + return $ans->{score} >= 1; } 1; From feb490ce06e227c7af1c64c6a5653c91387f82d8 Mon Sep 17 00:00:00 2001 From: Nathan Wallach Date: Thu, 2 Jun 2022 10:03:03 +0300 Subject: [PATCH 4/4] Fix a serious bug discovered by Glenn Rice - incorrect handling if the AnswerHints test "wrong" subroutine used to trigger hints returns a zero. Change the text of the error message issued when the "eval" fails, and make it a "warn" instead of a results table "answer message". --- macros/answerHints.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macros/answerHints.pl b/macros/answerHints.pl index f88f64a890..9ad34f7346 100644 --- a/macros/answerHints.pl +++ b/macros/answerHints.pl @@ -155,8 +155,8 @@ sub AnswerHints { { # Make the call to run the function inside an eval to trap errors my $myResult = 0; - eval { $myResult = &$wrong($correct, $student, $ans); } or do { - $ans->{ans_message} = "error during AnswerHints processing"; + eval { $myResult = &$wrong($correct, $student, $ans); 1; } or do { + warn "An error occurred in this problem."; last; }; if ($myResult) {