[system] / trunk / webwork-modperl / lib / WeBWorK / PG / PGtranslator.pm Repository:
ViewVC logotype

View of /trunk/webwork-modperl/lib/WeBWorK/PG/PGtranslator.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 402 - (download) (as text) (annotate)
Mon Jun 24 16:32:30 2002 UTC (10 years, 11 months ago) by malsyned
File size: 30250 byte(s)
Moved PGTranslator and IOGlue into WeBWorK::PG

    1 package PGtranslator;
    2 
    3 use strict;
    4 use warnings;
    5 use Opcode;
    6 use Safe;
    7 use Net::SMTP;
    8 use IOGlue;
    9 
   10 use Exporter;
   11 use DynaLoader;
   12 
   13 BEGIN {
   14   sub be_strict {   # allows the use of strict within macro packages.
   15     require 'strict.pm';
   16     strict::import();
   17   }
   18 }
   19 
   20 my @class_modules = ();
   21 
   22 sub new {
   23   my $class = shift;
   24   my $safe_cmpt = new Safe; #('PG_priv');
   25   my $self = {
   26     envir => undef,
   27     PG_PROBLEM_TEXT_ARRAY_REF => [],
   28     PG_PROBLEM_TEXT_REF => 0,
   29     PG_HEADER_TEXT_REF => 0,
   30     PG_ANSWER_HASH_REF => {},
   31     PG_FLAGS_REF => {},
   32     safe =>  $safe_cmpt,
   33     safe_compartment_name => $safe_cmpt->root,
   34     errors => "",
   35     source => "",
   36     rh_correct_answers => {},
   37     rh_student_answers => {},
   38     rh_evaluated_answers => {},
   39     rh_problem_result => {},
   40     rh_problem_state => {
   41       recorded_score => 0, # the score recorded in the data base
   42       num_of_correct_ans => 0, # the number of correct attempts at doing the problem
   43       num_of_incorrect_ans => 0, # the number of incorrect attempts
   44     },
   45     rf_problem_grader => \&std_problem_grader,
   46     rf_safety_filter => \&safetyFilter,
   47     ra_included_modules => [
   48       @class_modules
   49     ],
   50     rh_directories => {},
   51   };
   52   bless $self, $class;
   53 }
   54 
   55 sub evaluate_modules{
   56   my $self = shift;
   57   my @modules = @_;
   58   # temporary  -
   59   # We  need a method for setting the course directory without calling Global.
   60 
   61   my $courseScriptsDirectory = $self->rh_directories->{courseScriptsDirectory};
   62   my $save_SIG_die_trap = $SIG{__DIE__};
   63   local $SIG{__DIE__} = sub {CORE::die(@_) };
   64   while (@modules) {
   65     my $module_name = shift @modules;
   66     $module_name =~ s/\.pm$//;   # remove trailing .pm if someone forgot
   67     if ($module_name eq 'reset'  or $module_name eq 'erase' ) {
   68       @class_modules = ();
   69       next;
   70     }
   71     if ( -r  "${courseScriptsDirectory}${module_name}.pm"   ) {
   72       eval(qq! require "${courseScriptsDirectory}${module_name}.pm";  import ${module_name};! );
   73       warn "Errors in including the module ${courseScriptsDirectory}$module_name.pm $@" if $@;
   74     } else {
   75       eval(qq! require "${module_name}.pm";  import ${module_name};! );
   76       warn "Errors in including either the module $module_name.pm or ${courseScriptsDirectory}${module_name}.pm $@" if $@;
   77     }
   78     push(@class_modules, "\%${module_name}::");
   79     print STDERR "loading $module_name\n";
   80   }
   81   #$SIG{__DIE__} = $save_SIG_die_trap;
   82 }
   83 
   84 sub load_extra_packages{
   85   my $self = shift;
   86   my @package_list = @_;
   87   my $package_name;
   88 
   89     foreach $package_name (@package_list) {
   90         eval(qq! import ${package_name};! );
   91       warn "Errors in importing the package $package_name $@" if $@;
   92         push(@class_modules, "\%${package_name}::");
   93     }
   94 }
   95 
   96   ##############################################################################
   97           # SHARE variables and routines with safe compartment
   98 my %shared_subroutine_hash = (
   99   '&read_whole_problem_file' => 'PGtranslator', #the values are dummies.
  100   '&convertPath'  => 'PGtranslator',
  101   '&surePathToTmpFile' => 'PGtranslator',
  102   '&fileFromPath' => 'PGtranslator',
  103   '&directoryFromPath' => 'PGtranslator',
  104   '&createFile' => 'PGtranslator',
  105   '&PG_answer_eval' => 'PGtranslator',
  106   '&PG_restricted_eval' => 'PGtranslator',
  107   '&be_strict' => 'PGtranslator',
  108   '&send_mail_to' => 'PGtranslator',
  109   '&PGsort' => 'PGtranslator',
  110   '&dumpvar' => 'PGtranslator',
  111   '&includePGtext' => 'PGtranslator',
  112 );
  113 
  114 sub initialize {
  115     my $self = shift;
  116     my $safe_cmpt = $self->{safe};
  117     #print "initializing safeCompartment",$safe_cmpt -> root(), "\n";
  118 
  119     $safe_cmpt -> share(keys %shared_subroutine_hash);
  120     no strict;
  121     local(%envir) = %{ $self ->{envir} };
  122   $safe_cmpt -> share('%envir');
  123 #   local($rf_answer_eval) = sub { $self->PG_answer_eval(@_); };
  124 #   local($rf_restricted_eval) = sub { $self->PG_restricted_eval(@_); };
  125 #   $safe_cmpt -> share('$rf_answer_eval');
  126 #   $safe_cmpt -> share('$rf_restricted_eval');
  127 
  128   use strict;
  129 
  130     # end experiment
  131     $self->{ra_included_modules} = [@class_modules];
  132     $safe_cmpt -> share_from('main', $self->{ra_included_modules} ); #$self ->{ra_included_modules}
  133 
  134 }
  135 
  136 sub environment{
  137   my $self = shift;
  138   my $envirref = shift;
  139   if ( defined($envirref) )  {
  140     if (ref($envirref) eq 'HASH') {
  141       %{ $self -> {envir} } = %$envirref;
  142     } else {
  143       $self ->{errors} .= "ERROR: The environment method for PG_translate objects requires a reference to a hash";
  144     }
  145   }
  146   $self->{envir} ; #reference to current environment
  147 }
  148 
  149 sub mask {
  150   my $self = shift;
  151   my $mask = shift;
  152   my $safe_compartment = $self->{safe};
  153   $safe_compartment->mask($mask);
  154 }
  155 sub permit {
  156   my $self = shift;
  157   my @array = shift;
  158   my $safe_compartment = $self->{safe};
  159   $safe_compartment->permit(@array);
  160 }
  161 sub deny {
  162 
  163   my $self = shift;
  164   my @array = shift;
  165   my $safe_compartment = $self->{safe};
  166   $safe_compartment->deny(@array);
  167 }
  168 sub share_from {
  169   my $self = shift;
  170   my $pckg_name = shift;
  171   my $array_ref =shift;
  172   my $safe_compartment = $self->{safe};
  173   $safe_compartment->share_from($pckg_name,$array_ref);
  174 }
  175 
  176 sub source_string {
  177   my $self = shift;
  178   my $temp = shift;
  179   my $out;
  180   if ( ref($temp) eq 'SCALAR') {
  181     $self->{source} = $$temp;
  182     $out = $self->{source};
  183   } elsif ($temp) {
  184     $self->{source} = $temp;
  185     $out = $self->{source};
  186   }
  187   $self -> {source};
  188 }
  189 
  190 sub source_file {
  191   my $self = shift;
  192   my $filePath = shift;
  193   local(*SOURCEFILE);
  194   local($/);
  195   $/ = undef;   # allows us to treat the file as a single line
  196   my $err = "";
  197   if ( open(SOURCEFILE, "<$filePath") ) {
  198     $self -> {source} = <SOURCEFILE>;
  199     close(SOURCEFILE);
  200   } else {
  201     $self->{errors} .= "Can't open file: $filePath";
  202     croak( "Can't open file: $filePath\n" );
  203   }
  204 
  205 
  206 
  207   $err;
  208 }
  209 
  210 
  211 
  212 sub unrestricted_load {
  213   my $self = shift;
  214   my $filePath = shift;
  215   my $safe_cmpt = $self ->{safe};
  216   my $store_mask = $safe_cmpt->mask();
  217   $safe_cmpt->mask(Opcode::empty_opset());
  218   my $safe_cmpt_package_name = $safe_cmpt->root();
  219 
  220   my $macro_file_name = fileFromPath($filePath);
  221   $macro_file_name =~s/\.pl//;  # trim off the extenstion
  222   my $export_subroutine_name = "_${macro_file_name}_export";
  223     my $init_subroutine_name = "_${macro_file_name}_init";
  224     my $macro_file_loaded;
  225     my $local_errors = "";
  226     no strict;
  227     $macro_file_loaded  = defined(&{"${safe_cmpt_package_name}::$init_subroutine_name"} );
  228     print STDERR "$macro_file_name   has not yet been loaded\n" unless $macro_file_loaded;
  229   unless ($macro_file_loaded) {
  230     # print "loading $filePath\n";
  231     ## load the $filePath file
  232     ## Using rdo insures that the $filePath file is loaded for every problem, allowing initializations to occur.
  233     ## Ordinary mortals should not be fooling with the fundamental macros in these files.
  234     my $local_errors = "";
  235     if (-r $filePath ) {
  236       $safe_cmpt -> rdo( "$filePath" ) ;
  237       #warn "There were problems compiling the file: $filePath: <BR>--$@" if $@;
  238       $local_errors ="\nThere were problems compiling the file:\n $filePath\n $@\n" if $@;
  239       $self ->{errors} .= $local_errors if $local_errors;
  240       use strict;
  241     } else {
  242       $local_errors = "Can't open file $filePath for reading\n";
  243       $self ->{errors} .= $local_errors if $local_errors;
  244     }
  245     $safe_cmpt -> mask($store_mask);
  246 
  247   }
  248   $macro_file_loaded  = defined(&{"${safe_cmpt_package_name}::$init_subroutine_name"} );
  249   $local_errors .= "\nUnknown error.  Unable to load $filePath\n" if ($local_errors eq '' and not $macro_file_loaded);
  250   print STDERR "$filePath is properly loaded\n\n" if $macro_file_loaded;
  251     $local_errors;
  252 }
  253 
  254 sub nameSpace {
  255   my $self = shift;
  256   $self->{safe}->root;
  257 }
  258 
  259 sub a_text {
  260   my $self  = shift;
  261     @{$self->{PG_PROBLEM_TEXT_ARRAY_REF}};
  262 }
  263 
  264 sub header {
  265   my $self = shift;
  266   ${$self->{PG_HEADER_TEXT_REF}};
  267 }
  268 
  269 sub h_flags {
  270   my $self = shift;
  271   %{$self->{PG_FLAGS_REF}};
  272 }
  273 
  274 sub rh_flags {
  275   my $self = shift;
  276   $self->{PG_FLAGS_REF};
  277 }
  278 sub h_answers{
  279   my $self = shift;
  280   %{$self->{PG_ANSWER_HASH_REF}};
  281 }
  282 
  283 sub ra_text {
  284   my $self  = shift;
  285     $self->{PG_PROBLEM_TEXT_ARRAY_REF};
  286 
  287 }
  288 
  289 sub r_text {
  290   my $self  = shift;
  291     $self->{PG_PROBLEM_TEXT_REF};
  292 }
  293 
  294 sub r_header {
  295   my $self = shift;
  296   $self->{PG_HEADER_TEXT_REF};
  297 }
  298 
  299 sub rh_directories {
  300   my $self = shift;
  301   my $rh_directories = shift;
  302   $self->{rh_directories}=$rh_directories if ref($rh_directories) eq 'HASH';
  303   $self->{rh_directories};
  304 }
  305 
  306 sub rh_correct_answers {
  307   my $self = shift;
  308   my @in = @_;
  309   return $self->{rh_correct_answers} if @in == 0;
  310 
  311   if ( ref($in[0]) eq 'HASH' ) {
  312     $self->{rh_correct_answers} = { %{ $in[0] } }; # store a copy of the hash
  313   } else {
  314     $self->{rh_correct_answers} = { @in }; # store a copy of the hash
  315   }
  316   $self->{rh_correct_answers}
  317 }
  318 
  319 sub rf_problem_grader {
  320   my $self = shift;
  321   my $in = shift;
  322   return $self->{rf_problem_grader} unless defined($in);
  323   if (ref($in) =~/CODE/ ) {
  324     $self->{rf_problem_grader} = $in;
  325   } else {
  326     die "ERROR: Attempted to install a problem grader which was not a reference to a subroutine.";
  327   }
  328   $self->{rf_problem_grader}
  329 }
  330 
  331 
  332 sub errors{
  333   my $self = shift;
  334   $self->{errors};
  335 }
  336 
  337 ##############################################################################
  338 
  339           ## restrict the operations allowed within the safe compartment
  340 
  341 sub set_mask {
  342   my $self = shift;
  343   my $safe_cmpt = $self ->{safe};
  344     $safe_cmpt->mask(Opcode::full_opset());  # allow no operations
  345     $safe_cmpt->permit(qw(   :default ));
  346     $safe_cmpt->permit(qw(time));  # used to determine whether solutions are visible.
  347   $safe_cmpt->permit(qw( atan2 sin cos exp log sqrt ));
  348 
  349   # just to make sure we'll deny some things specifically
  350   $safe_cmpt->deny(qw(entereval));
  351   $safe_cmpt->deny(qw (  unlink symlink system exec ));
  352   $safe_cmpt->deny(qw(print require));
  353 }
  354 
  355 ############################################################################
  356 
  357 
  358 sub translate {
  359   my $self = shift;
  360   my @PROBLEM_TEXT_OUTPUT = ();
  361   my $safe_cmpt = $self ->{safe};
  362   my $evalString = $self -> {source};
  363   $self ->{errors} .= qq{ERROR:  This problem file was empty!\n} unless ($evalString) ;
  364   $self ->{errors} .= qq{ERROR:  You must define the environment before translating.}
  365        unless defined( $self->{envir} );
  366     # reset the error detection
  367     my $save_SIG_die_trap = $SIG{__DIE__};
  368     $SIG{__DIE__} = sub {CORE::die(@_) };
  369 
  370 ############################################################################
  371 
  372 
  373         ##########################################
  374         ###### PG preprocessing code #############
  375         ##########################################
  376             # BEGIN_TEXT and END_TEXT must occur on a line by themselves.
  377             $evalString =~ s/\n\s*END_TEXT[\s;]*\n/\nEND_TEXT\n/g;
  378           $evalString =~ s/\n\s*BEGIN_TEXT[\s;]*\n/\nTEXT\(EV3\(<<'END_TEXT'\)\);\n/g;
  379           $evalString =~ s/ENDDOCUMENT.*/ENDDOCUMENT();/s; # remove text after ENDDOCUMENT
  380 
  381         $evalString =~ s/\\/\\\\/g;    # \ can't be used for escapes because of TeX conflict
  382             $evalString =~ s/~~/\\/g;      # use ~~ as escape instead, use # for comments
  383 
  384         my ($PG_PROBLEM_TEXT_REF, $PG_HEADER_TEXT_REF, $PG_ANSWER_HASH_REF, $PG_FLAGS_REF)
  385               =$safe_cmpt->reval("   $evalString");
  386 
  387 # This section could use some more error messages.  In particular if a problem doesn't produce the right output, the user needs
  388 # information about which problem was at fault.
  389 #
  390 #
  391 
  392         $self->{errors} .= $@;
  393 #         push(@PROBLEM_TEXT_OUTPUT   ,   split(/(\n)/,$$PG_PROBLEM_TEXT_REF)  ) if  defined($$PG_PROBLEM_TEXT_REF  );
  394           push(@PROBLEM_TEXT_OUTPUT   ,   split(/^/,$$PG_PROBLEM_TEXT_REF)  ) if  ref($PG_PROBLEM_TEXT_REF  ) eq 'SCALAR';
  395                                                                            ## This is better than using defined($$PG_PROBLEM_TEXT_REF)
  396                                                                            ## Because more pleasant feedback is given
  397                                                                            ## when the problem doesn't render.
  398            # try to get the \n to appear at the end of the line
  399 
  400         use strict;
  401         #############################################################################
  402         ##########  end  EVALUATION code                                  ###########
  403         #############################################################################
  404 
  405         ##########################################
  406     ###### PG error processing code ##########
  407     ##########################################
  408         my (@input,$lineNumber,$line);
  409         if ($self -> {errors}) {
  410                 #($self -> {errors}) =~ s/</&lt/g;
  411                 #($self -> {errors}) =~ s/>/&gt/g;
  412            #try to clean up errors so they will look ok
  413                 $self ->{errors} =~ s/\[.+?\.pl://gm;   #erase [Fri Dec 31 12:58:30 1999] processProblem7.pl:
  414                 #$self -> {errors} =~ s/eval\s+'(.|[\n|r])*$//;
  415             #end trying to clean up errors so they will look ok
  416 
  417 
  418                 push(@PROBLEM_TEXT_OUTPUT   ,  qq!\n<A NAME="problem! .
  419                     $self->{envir} ->{'probNum'} .
  420                     qq!"><PRE>        Problem!.
  421                     $self->{envir} ->{'probNum'}.
  422                     qq!\nERROR caught by PGtranslator while processing problem file:! .
  423                   $self->{envir}->{'probFileName'}.
  424                   "\n****************\r\n" .
  425                   $self -> {errors}."\r\n" .
  426                   "****************<BR>\n");
  427 
  428                 push(@PROBLEM_TEXT_OUTPUT   , "------Input Read\r\n");
  429                $self->{source} =~ s/</&lt;/g;
  430                @input=split("\n", $self->{source});
  431                $lineNumber = 1;
  432                 foreach $line (@input) {
  433                     chomp($line);
  434                     push(@PROBLEM_TEXT_OUTPUT, "$lineNumber\t\t$line\r\n");
  435                     $lineNumber ++;
  436                 }
  437                 push(@PROBLEM_TEXT_OUTPUT  ,"\n-----<BR></PRE>\r\n");
  438 
  439 
  440 
  441         }
  442 
  443 
  444         ## we need to make sure that the other output variables are defined
  445 
  446                 ## If the eval failed with errors, one or more of these variables won't be defined.
  447                 $PG_ANSWER_HASH_REF = {}      unless defined($PG_ANSWER_HASH_REF);
  448                 $PG_HEADER_TEXT_REF = \( "" ) unless defined($PG_HEADER_TEXT_REF);
  449                 $PG_FLAGS_REF = {}            unless defined($PG_FLAGS_REF);
  450 
  451             $PG_FLAGS_REF->{'error_flag'} = 1     if $self -> {errors};
  452         my $PG_PROBLEM_TEXT                     = join("",@PROBLEM_TEXT_OUTPUT);
  453 
  454         $self ->{ PG_PROBLEM_TEXT_REF }     = \$PG_PROBLEM_TEXT;
  455         $self ->{ PG_PROBLEM_TEXT_ARRAY_REF }   = \@PROBLEM_TEXT_OUTPUT;
  456       $self ->{ PG_HEADER_TEXT_REF  }   = $PG_HEADER_TEXT_REF;
  457       $self ->{ rh_correct_answers  }   = $PG_ANSWER_HASH_REF;
  458       $self ->{ PG_FLAGS_REF      }   = $PG_FLAGS_REF;
  459       $SIG{__DIE__} = $save_SIG_die_trap;
  460       $self ->{errors};
  461 }  # end translate
  462 
  463 
  464 sub rh_evaluated_answers {
  465   my $self = shift;
  466   my @in = @_;
  467   return $self->{rh_evaluated_answers} if @in == 0;
  468 
  469   if ( ref($in[0]) eq 'HASH' ) {
  470     $self->{rh_evaluated_answers} = { %{ $in[0] } }; # store a copy of the hash
  471   } else {
  472     $self->{rh_evaluated_answers} = { @in }; # store a copy of the hash
  473   }
  474   $self->{rh_evaluated_answers};
  475 }
  476 sub rh_problem_result {
  477   my $self = shift;
  478   my @in = @_;
  479   return $self->{rh_problem_result} if @in == 0;
  480 
  481   if ( ref($in[0]) eq 'HASH' ) {
  482     $self->{rh_problem_result} = { %{ $in[0] } }; # store a copy of the hash
  483   } else {
  484     $self->{rh_problem_result} = { @in }; # store a copy of the hash
  485   }
  486   $self->{rh_problem_result};
  487 }
  488 sub rh_problem_state {
  489   my $self = shift;
  490   my @in = @_;
  491   return $self->{rh_problem_state} if @in == 0;
  492 
  493   if ( ref($in[0]) eq 'HASH' ) {
  494     $self->{rh_problem_state} = { %{ $in[0] } }; # store a copy of the hash
  495   } else {
  496     $self->{rh_problem_state} = { @in }; # store a copy of the hash
  497   }
  498   $self->{rh_problem_state};
  499 }
  500 
  501 
  502 sub process_answers{
  503   my $self = shift;
  504   my @in = @_;
  505   my %h_student_answers;
  506   if (ref($in[0]) eq 'HASH' ) {
  507     %h_student_answers = %{ $in[0] };  #receiving a reference to a hash of answers
  508   } else {
  509     %h_student_answers = @in;          # receiving a hash of answers
  510   }
  511   my $rh_correct_answers = $self->rh_correct_answers();
  512   my @answer_entry_order = ( defined($self->{PG_FLAGS_REF}->{ANSWER_ENTRY_ORDER}) ) ?
  513                         @{$self->{PG_FLAGS_REF}->{ANSWER_ENTRY_ORDER}} : keys %{$rh_correct_answers};
  514 
  515   # apply each instructors answer to the corresponding student answer
  516 
  517   foreach my $ans_name ( @answer_entry_order ) {
  518       my ($ans, $errors) = $self->filter_answer( $h_student_answers{$ans_name} );
  519       no strict;
  520       # evaluate the answers inside the safe compartment.
  521       local($rf_fun,$temp_ans) = (undef,undef);
  522       if ( defined($rh_correct_answers ->{$ans_name} ) ) {
  523         $rf_fun  = $rh_correct_answers->{$ans_name};
  524       } else {
  525         warn "There is no answer evaluator for the question labeled $ans_name";
  526       }
  527       $temp_ans  = $ans;
  528       $temp_ans = '' unless defined($temp_ans);  #make sure that answer is always defined
  529                                                 # in case the answer evaluator forgets to check
  530       $self->{safe}->share('$rf_fun','$temp_ans');
  531 
  532         # reset the error detection
  533       my $save_SIG_die_trap = $SIG{__DIE__};
  534       $SIG{__DIE__} = sub {CORE::die(@_) };
  535       my $rh_ans_evaluation_result;
  536         if (ref($rf_fun) eq 'CODE' ) {
  537           $rh_ans_evaluation_result = $self->{safe} ->reval( '&{ $rf_fun }($temp_ans)' ) ;
  538           warn "Error in PGtranslator.pm::process_answers: Answer $ans_name:<BR>\n $@\n" if $@;
  539         } elsif (ref($rf_fun) eq 'AnswerEvaluator')   {
  540           $rh_ans_evaluation_result = $self->{safe} ->reval('$rf_fun->evaluate($temp_ans)');
  541           warn "Error in PGtranslator.pm::process_answers: Answer $ans_name:<BR>\n $@\n" if $@;
  542           warn "Evaluation error: Answer $ans_name:<BR>\n", $rh_ans_evaluation_result->error_flag(), " :: ",$rh_ans_evaluation_result->error_message(),"<BR>\n"
  543                        if defined($rh_ans_evaluation_result)  and defined($rh_ans_evaluation_result->error_flag());
  544         } else {
  545           warn "Error in PGtranslator5.pm::process_answers: Answer $ans_name:<BR>\n Unrecognized evaluator type |", ref($rf_fun), "|";
  546         }
  547 
  548         $SIG{__DIE__} = $save_SIG_die_trap;
  549 
  550 
  551         use strict;
  552         unless ( ( ref($rh_ans_evaluation_result) eq 'HASH') or ( ref($rh_ans_evaluation_result) eq 'AnswerHash') ) {
  553           warn "Error in PGtranslator5.pm::process_answers: Answer $ans_name:<BR>\n
  554                 Answer evaluators must return a hash or an AnswerHash type, not type |",
  555                 ref($rh_ans_evaluation_result), "|";
  556         }
  557         $rh_ans_evaluation_result ->{ans_message} .= "$errors \n" if $errors;
  558         $rh_ans_evaluation_result ->{ans_name} = $ans_name;
  559       $self->{rh_evaluated_answers}->{$ans_name} = $rh_ans_evaluation_result;
  560 
  561   }
  562   $self->rh_evaluated_answers;
  563 
  564 }
  565 
  566 sub grade_problem {
  567   my $self = shift;
  568     my %form_options = @_;
  569   my $rf_grader = $self->{rf_problem_grader};
  570   ($self->{rh_problem_result},$self->{rh_problem_state} )  =
  571                     &{$rf_grader}(  $self -> {rh_evaluated_answers},
  572                                     $self -> {rh_problem_state},
  573                                     %form_options
  574                                   );
  575 
  576   ($self->{rh_problem_result}, $self->{rh_problem_state} ) ;
  577 }
  578 
  579 sub rf_std_problem_grader {
  580     my $self = shift;
  581   return \&std_problem_grader;
  582 }
  583 sub old_std_problem_grader{
  584   my $rh_evaluated_answers = shift;
  585   my %flags = @_;  # not doing anything with these yet
  586   my %evaluated_answers = %{$rh_evaluated_answers};
  587   my  $allAnswersCorrectQ=1;
  588   foreach my $ans_name (keys %evaluated_answers) {
  589   # I'm not sure if this check is really useful.
  590       if (ref($evaluated_answers{$ans_name} ) eq 'HASH' ) {
  591         $allAnswersCorrectQ = 0 unless( 1 == $evaluated_answers{$ans_name}->{score} );
  592       } else {
  593         warn "Error: Answer $ans_name is not a hash";
  594         warn "$evaluated_answers{$ans_name}";
  595       }
  596   }
  597   # Notice that "all answers are correct" if there are no questions.
  598   { score       => $allAnswersCorrectQ,
  599     prev_tries    => 0,
  600     partial_credit  => $allAnswersCorrectQ,
  601     errors      =>  "",
  602     type              => 'old_std_problem_grader',
  603     flags       => {}, # not doing anything with these yet
  604   };  # hash output
  605 
  606 }
  607 
  608 #####################################
  609 # This is a model for plug-in problem graders
  610 #####################################
  611 
  612 sub std_problem_grader{
  613   my $rh_evaluated_answers = shift;
  614   my $rh_problem_state = shift;
  615   my %form_options = @_;
  616   my %evaluated_answers = %{$rh_evaluated_answers};
  617   #  The hash $rh_evaluated_answers typically contains:
  618   #      'answer1' => 34, 'answer2'=> 'Mozart', etc.
  619 
  620   # By default the  old problem state is simply passed back out again.
  621   my %problem_state = %$rh_problem_state;
  622 
  623 
  624   # %form_options might include
  625   # The user login name
  626   # The permission level of the user
  627   # The studentLogin name for this psvn.
  628   # Whether the form is asking for a refresh or is submitting a new answer.
  629 
  630   # initial setup of the answer
  631   my %problem_result = ( score        => 0,
  632                errors         => '',
  633                type           => 'std_problem_grader',
  634                msg          => '',
  635              );
  636   # Checks
  637 
  638   my $ansCount = keys %evaluated_answers;  # get the number of answers
  639   unless ($ansCount > 0 ) {
  640     $problem_result{msg} = "This problem did not ask any questions.";
  641     return(\%problem_result,\%problem_state);
  642   }
  643 
  644   if ($ansCount > 1 ) {
  645     $problem_result{msg} = 'In order to get credit for this problem all answers must be correct.' ;
  646   }
  647 
  648   unless (defined( $form_options{answers_submitted}) and $form_options{answers_submitted} == 1) {
  649     return(\%problem_result,\%problem_state);
  650   }
  651 
  652   my  $allAnswersCorrectQ=1;
  653   foreach my $ans_name (keys %evaluated_answers) {
  654   # I'm not sure if this check is really useful.
  655       if ( ( ref($evaluated_answers{$ans_name} ) eq 'HASH' ) or ( ref($evaluated_answers{$ans_name}) eq 'AnswerHash' ) ) {
  656         $allAnswersCorrectQ = 0 unless( 1 == $evaluated_answers{$ans_name}->{score} );
  657       } else {
  658         warn "Error: Answer $ans_name is not a hash";
  659         warn "$evaluated_answers{$ans_name}";
  660         warn "This probably means that the answer evaluator is for this answer is not working correctly.";
  661         $problem_result{error} = "Error: Answer $ans_name is not a hash: $evaluated_answers{$ans_name}";
  662       }
  663   }
  664   # report the results
  665   $problem_result{score} = $allAnswersCorrectQ;
  666 
  667   # I don't like to put in this bit of code.
  668   # It makes it hard to construct error free problem graders
  669   # I would prefer to know that the problem score was numeric.
  670     unless ($problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) {
  671       $problem_state{recorded_score} = 0;  # This gets rid of non-numeric scores
  672     }
  673     #
  674   if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) {
  675     $problem_state{recorded_score} = 1;
  676   } else {
  677     $problem_state{recorded_score} = 0;
  678   }
  679 
  680   $problem_state{num_of_correct_ans}++ if $allAnswersCorrectQ == 1;
  681   $problem_state{num_of_incorrect_ans}++ if $allAnswersCorrectQ == 0;
  682   (\%problem_result, \%problem_state);
  683 }
  684 sub rf_avg_problem_grader {
  685     my $self = shift;
  686   return \&avg_problem_grader;
  687 }
  688 sub avg_problem_grader{
  689     my $rh_evaluated_answers = shift;
  690   my $rh_problem_state = shift;
  691   my %form_options = @_;
  692   my %evaluated_answers = %{$rh_evaluated_answers};
  693   #  The hash $rh_evaluated_answers typically contains:
  694   #      'answer1' => 34, 'answer2'=> 'Mozart', etc.
  695 
  696   # By default the  old problem state is simply passed back out again.
  697   my %problem_state = %$rh_problem_state;
  698 
  699 
  700   # %form_options might include
  701   # The user login name
  702   # The permission level of the user
  703   # The studentLogin name for this psvn.
  704   # Whether the form is asking for a refresh or is submitting a new answer.
  705 
  706   # initial setup of the answer
  707   my  $total=0;
  708   my %problem_result = ( score        => 0,
  709                errors         => '',
  710                type           => 'avg_problem_grader',
  711                msg          => '',
  712              );
  713     my $count = keys %evaluated_answers;
  714     $problem_result{msg} = 'You can earn partial credit on this problem.' if $count >1;
  715     # Return unless answers have been submitted
  716     unless ($form_options{answers_submitted} == 1) {
  717     return(\%problem_result,\%problem_state);
  718   }
  719   # Answers have been submitted -- process them.
  720   foreach my $ans_name (keys %evaluated_answers) {
  721     $total += $evaluated_answers{$ans_name}->{score};
  722   }
  723   # Calculate score rounded to three places to avoid roundoff problems
  724   $problem_result{score} = $total/$count if $count;
  725   # increase recorded score if the current score is greater.
  726   $problem_state{recorded_score} = $problem_result{score} if $problem_result{score} > $problem_state{recorded_score};
  727 
  728 
  729     $problem_state{num_of_correct_ans}++ if $total == $count;
  730   $problem_state{num_of_incorrect_ans}++ if $total < $count ;
  731   warn "Error in grading this problem the total $total is larger than $count" if $total > $count;
  732   (\%problem_result, \%problem_state);
  733 
  734 }
  735 =head3 safetyFilter
  736 
  737   ($filtered_ans, $errors) = $obj ->filter_ans($ans)
  738                                $obj ->rf_safety_filter()
  739 
  740 =cut
  741 
  742 sub filter_answer {
  743   my $self = shift;
  744   my $ans = shift;
  745   my @filtered_answers;
  746   my $errors='';
  747   if (ref($ans) eq 'ARRAY') {   #handle the case where the answer comes from several inputs with the same name
  748                   # In many cases this will be passed as a reference to an array
  749                   # if it is passed as a single string (separated by \0 characters) as
  750                   # some early versions of CGI behave, then
  751                   # it is unclear what will happen when the answer is filtered.
  752     foreach my $item (@{$ans}) {
  753       my ($filtered_ans, $error) = &{ $self->{rf_safety_filter} } ($item);
  754       push(@filtered_answers, $filtered_ans);
  755       $errors .= " ". $error if $error;  # add error message if error is non-zero.
  756     }
  757     (\@filtered_answers,$errors);
  758 
  759   } else {
  760     &{ $self->{rf_safety_filter} } ($ans);
  761   }
  762 
  763 }
  764 sub rf_safety_filter {
  765   my $self = shift;
  766   my $rf_filter = shift;
  767   $self->{rf_safety_filter} = $rf_filter if $rf_filter and ref($rf_filter) eq 'CODE';
  768   warn "The safety_filter must be a reference to a subroutine" unless ref($rf_filter) eq 'CODE' ;
  769   $self->{rf_safety_filter}
  770 }
  771 sub safetyFilter {
  772       my $answer = shift;  # accepts one answer and checks it
  773       my $submittedAnswer = $answer;
  774     $answer = '' unless defined $answer;
  775     my ($errorno);
  776     $answer =~ tr/\000-\037/ /;
  777    #### Return if answer field is empty ########
  778     unless ($answer =~ /\S/) {
  779 #     $errorno = "<BR>No answer was submitted.";
  780             $errorno = 0;  ## don't report blank answer as error
  781 
  782       return ($answer,$errorno);
  783       }
  784    ######### replace ^ with **    (for exponentiation)
  785    #  $answer =~ s/\^/**/g;
  786    ######### Return if  forbidden characters are found
  787     unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)]+$/ )  {
  788       $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
  789       $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>";
  790 
  791       return ($answer,$errorno);
  792       }
  793 
  794     $errorno = 0;
  795     return($answer, $errorno);
  796 }
  797 
  798 sub PGsort {
  799   my $sort_order = shift;
  800   die "Must supply an ordering function with PGsort: PGsort sub {\$a cmp \$b }, \@list\n" unless ref($sort_order) eq 'CODE';
  801   sort {&$sort_order($a,$b)} @_;
  802 }
  803 
  804 no strict;   # this is important -- I guess because eval operates on code which is not written with strict in mind.
  805 
  806 sub PG_restricted_eval {
  807     my $string = shift;
  808     my ($pck,$file,$line) = caller;
  809     my $save_SIG_warn_trap = $SIG{__WARN__};
  810     $SIG{__WARN__} = sub { CORE::die @_};
  811     my $save_SIG_die_trap = $SIG{__DIE__};
  812     $SIG{__DIE__}= sub {CORE::die @_};
  813     no strict;
  814     my $out = eval  ("package main; " . $string );
  815     my $errors =$@;
  816     my $full_error_report = "PG_restricted_eval detected error at line $line of file $file \n"
  817                 . $errors .
  818                 "The calling package is $pck\n" if defined($errors) && $errors =~/\S/;
  819     use strict;
  820     $SIG{__DIE__} = $save_SIG_die_trap;
  821     $SIG{__WARN__} = $save_SIG_warn_trap;
  822     return (wantarray) ?  ($out, $errors,$full_error_report) : $out;
  823 }
  824 
  825 sub PG_answer_eval {
  826    local($string) = shift;   # I made this local just in case -- see PG_estricted_eval
  827    my $errors = '';
  828    my $full_error_report = '';
  829    my ($pck,$file,$line) = caller;
  830     # Because of the global variable $PG::compartment_name and $PG::safe_cmpt
  831     # only one problem safe compartment can be active at a time.
  832     # This might cause problems at some point.  In that case a cleverer way
  833     # of insuring that the package stays in scope until the answer is evaluated
  834     # will be required.
  835 
  836     # This is pretty tricky and doesn't always work right.
  837     # We seem to need PG_priv instead of main when PG_answer_eval is called within a completion
  838     # 'package PG_priv; '
  839     my $save_SIG_warn_trap = $SIG{__WARN__};
  840     $SIG{__WARN__} = sub { CORE::die @_};
  841     my $save_SIG_die_trap = $SIG{__DIE__};
  842     $SIG{__DIE__}= sub {CORE::die @_};
  843     my $save_SIG_FPE_trap= $SIG{'FPE'};
  844     #$SIG{'FPE'} = \&main::PG_floating_point_exception_handler;
  845     #$SIG{'FPE'} = sub {exit(0)};
  846     no strict;
  847     my $out = eval('package main;'.$string);
  848     $out = '' unless defined($out);
  849     $errors .=$@;
  850 
  851     $full_error_report = "ERROR: at line $line of file $file
  852                 $errors
  853                 The calling package is $pck\n" if defined($errors) && $errors =~/\S/;
  854     use strict;
  855     $SIG{__DIE__} = $save_SIG_die_trap;
  856     $SIG{__WARN__} = $save_SIG_warn_trap;
  857     $SIG{'FPE'} = $save_SIG_FPE_trap;
  858     return (wantarray) ?  ($out, $errors,$full_error_report) : $out;
  859 
  860 
  861 }
  862 
  863 sub dumpvar {
  864     my ($packageName) = @_;
  865 
  866     local(*alias);
  867 
  868     sub emit {
  869       print @_;
  870     }
  871 
  872     *stash = *{"${packageName}::"};
  873     $, = "  ";
  874 
  875     emit "Content-type: text/html\n\n<PRE>\n";
  876 
  877 
  878     while ( ($varName, $globValue) = each %stash) {
  879         emit "$varName\n";
  880 
  881   *alias = $globValue;
  882   next if $varName=~/main/;
  883 
  884   if (defined($alias) ) {
  885       emit "  \$$varName $alias \n";
  886   }
  887 
  888   if ( defined(@alias) ) {
  889       emit "  \@$varName @alias \n";
  890   }
  891   if (defined(%alias) ) {
  892       emit "  %$varName \n";
  893       foreach $key (keys %alias) {
  894           emit "    $key => $alias{$key}\n";
  895       }
  896 
  897 
  898 
  899   }
  900     }
  901     emit "</PRE></PRE>";
  902 
  903 
  904 }
  905 use strict;
  906 
  907 #### for error checking and debugging purposes
  908 sub pretty_print_rh {
  909   my $rh = shift;
  910   foreach my $key (sort keys %{$rh})  {
  911     warn "  $key => ",$rh->{$key},"\n";
  912   }
  913 }
  914 # end evaluation subroutines
  915 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9