[system] / trunk / webwork2 / lib / WeBWorK / PG / Translator.pm Repository:
ViewVC logotype

View of /trunk/webwork2/lib/WeBWorK/PG/Translator.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 562 - (download) (as text) (annotate)
Fri Sep 27 23:53:42 2002 UTC (10 years, 7 months ago) by sh002i
File size: 45173 byte(s)
- created macros/IO.pl, which is loaded with no opmask by PG.pm. It is a copy
  of WeBWorK::PG::IO.pm, with some changes to make it work as a macro package.
  The translator no longer shares IO.pm's functions with the safe compartment.
  This is a BAD THING, and should be reconsidered when the Translator is
  revised.
- Changed many (but not all) checks for HTML or HTML_tth modes to match /^HTML/
  in the macros.
- changed &header to &head in Problem.pm
- Added problem environment variables for gif2eps and png2eps and modified
  &dangerousMacros::alias to use them
- fixed MOST of the harmless warnings in the system. there's still the "Use
  of uninitialized value in null operation" warning in template(), tho.

Still to come:

- make images in PDFs work
- fix TTH mode character encodings on mac (maybe)
- have logout button invalidate key
- Pretty die messages (from outside of the translator)
- Feedback - need nice modular way of sending email
- Options - email address and password

    1 ################################################################################
    2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
    3 # $Id$
    4 ################################################################################
    5 
    6 
    7 package WeBWorK::PG::Translator;
    8 
    9 
   10 use strict;
   11 use warnings;
   12 use Opcode;
   13 use Safe;
   14 use Net::SMTP;
   15 use WeBWorK::Utils qw(runtime_use);
   16 use WeBWorK::PG::IO;
   17 
   18 # loading GD within the Safe compartment has occasionally caused infinite recursion
   19 # Putting these use statements here seems to avoid this problem
   20 # It is not clear that this is essential once things are working properly.
   21 #use Exporter;
   22 #use DynaLoader;
   23 
   24 
   25 =head1 NAME
   26 
   27 WeBWorK::PG::Translator - Evaluate PG code and evaluate answers safely
   28 
   29 =head1 SYNPOSIS
   30 
   31     my $pt = new WeBWorK::PG::Translator;      # create a translator;
   32     $pt->environment(\%envir);      # provide the environment variable for the problem
   33     $pt->initialize();              # initialize the translator
   34     $pt-> set_mask();               # set the operation mask for the translator safe compartment
   35     $pt->source_string($source);    # provide the source string for the problem
   36 
   37     $pt -> unrestricted_load("${courseScriptsDirectory}PG.pl");
   38     $pt -> unrestricted_load("${courseScriptsDirectory}dangerousMacros.pl");
   39                                     # load the unprotected macro files
   40                                     # these files are evaluated with the Safe compartment wide open
   41                                     # other macros are loaded from within the problem using loadMacros
   42 
   43     $pt ->translate();              # translate the problem (the out following 4 pieces of information are created)
   44 
   45     $PG_PROBLEM_TEXT_ARRAY_REF = $pt->ra_text();              # output text for the body of the HTML file (in array form)
   46     $PG_PROBLEM_TEXT_REF = $pt->r_text();                     # output text for the body of the HTML file
   47     $PG_HEADER_TEXT_REF = $pt->r_header;#\$PG_HEADER_TEXT;    # text for the header of the HTML file
   48     $PG_ANSWER_HASH_REF = $pt->rh_correct_answers;            # a hash of answer evaluators
   49     $PG_FLAGS_REF = $pt ->rh_flags;                           # misc. status flags.
   50 
   51     $pt -> process_answers(\%inputs);    # evaluates all of the answers using submitted answers from %input
   52 
   53     my $rh_answer_results = $pt->rh_evaluated_answers;  # provides a hash of the results of evaluating the answers.
   54     my $rh_problem_result = $pt->grade_problem;         # grades the problem using the default problem grading method.
   55 
   56 =head1 DESCRIPTION
   57 
   58 This module defines an object which will translate a problem written in the Problem Generating (PG) language
   59 
   60 =cut
   61 
   62 =head2 be_strict
   63 
   64 This creates a substitute for C<use strict;> which cannot be used in PG problem
   65 sets or PG macro files.  Use this way to imitate the behavior of C<use strict;>
   66 
   67   BEGIN {
   68     be_strict(); # an alias for use strict.
   69                  # This means that all global variable
   70                  # must contain main:: as a prefix.
   71   }
   72 
   73 =cut
   74 
   75 BEGIN {
   76   # allows the use of strict within macro packages.
   77   sub be_strict {
   78     require 'strict.pm';
   79     strict::import();
   80   }
   81 
   82   # also define in Main::, for PG modules.
   83   sub Main::be_strict { &be_strict }
   84 }
   85 
   86 =head2 evaluate_modules
   87 
   88   Usage:  $obj -> evaluate_modules('WWPlot', 'Fun', 'Circle');
   89           $obj -> evaluate_modules('reset');
   90 
   91 Adds the modules WWPlot.pm, Fun.pm and Circle.pm in the courseScripts directory to the list of modules
   92 which can be used by the PG problems.  The keyword 'reset' or 'erase' erases the list of modules already loaded
   93 
   94 =cut
   95 
   96 #my @class_modules = ();
   97 #sub evaluate_modules{
   98 # my $self = shift;
   99 # my @modules = @_;
  100 # # temporary  -
  101 # # We  need a method for setting the course directory without calling Global.
  102 #
  103 # my $courseScriptsDirectory = $self->rh_directories->{courseScriptsDirectory};
  104 # my $save_SIG_die_trap = $SIG{__DIE__};
  105 # $SIG{__DIE__} = sub {CORE::die(@_) };
  106 # while (@modules) {
  107 #   my $module_name = shift @modules;
  108 #   print STDERR "evaluate_modules: about to evaluate $module_name\n";
  109 #   $module_name =~ s/\.pm$//;   # remove trailing .pm if someone forgot
  110 #   if ($module_name eq 'reset'  or $module_name eq 'erase' ) {
  111 #     @class_modules = ();
  112 #     next;
  113 #   }
  114 #   if ( -r  "${courseScriptsDirectory}${module_name}.pm"   ) {
  115 #     eval(qq! require "${courseScriptsDirectory}${module_name}.pm";  import ${module_name};! );
  116 #     warn "Errors in including the module ${courseScriptsDirectory}$module_name.pm $@" if $@;
  117 #   } else {
  118 #     eval(qq! require "${module_name}.pm";  import ${module_name};! );
  119 #     warn "Errors in including either the module $module_name.pm or ${courseScriptsDirectory}${module_name}.pm $@" if $@;
  120 #   }
  121 #   push(@class_modules, "\%${module_name}::");
  122 #   print STDERR "loading $module_name\n";
  123 # }
  124 # $SIG{__DIE__} = $save_SIG_die_trap;
  125 #}
  126 
  127 # *** attention! Right now, packages DO NOT have the WeBWorK::PG prefix
  128 # before release, we HAVE to figure out how to make them behave WITH the
  129 # prefix -- this may involve changing the actual package names.
  130 
  131 sub evaluate_modules {
  132   my $self = shift;
  133   local $SIG{__DIE__} = "DEFAULT"; # we're going to be eval()ing code
  134   foreach (@_) {
  135     #warn "attempting to load $_\n";
  136     # ensure that the name is in fact a base name
  137     s/\.pm$// and warn "fixing your broken package name: $_.pm => $_";
  138 #   # generate a full package name from the base name
  139 #   unless (/::/) {
  140 #     $_ = "WeBWorK::PG::$_";
  141 #   }
  142     # call runtime_use on the package name
  143     # don't worry -- runtime_use won't load a package twice!
  144     eval { runtime_use $_ };
  145     warn "Failed to evaluate module $_: $@" if $@;
  146     # record this in the appropriate place
  147     push @{$self->{ra_included_modules}}, "\%${_}::";
  148   }
  149 }
  150 
  151 =head2 load_extra_packages
  152 
  153   Usage:  $obj -> load_extra_packages('AlgParserWithImplicitExpand',
  154                                       'Expr','ExprWithImplicitExpand');
  155 
  156 Loads extra packages for modules that contain more than one package.  Works in conjunction with
  157 evaluate_modules.  It is assumed that the file containing the extra packages (along with the base
  158 pachage name which is the same as the name of the file minus the .pm extension) has already been
  159 loaded using evaluate_modules
  160 =cut
  161 
  162 sub load_extra_packages{
  163   my $self = shift;
  164   my @package_list = @_;
  165   my $package_name;
  166 
  167   foreach (@package_list) {
  168     # ensure that the name is in fact a base name
  169     s/\.pm$// and warn "fixing your broken package name: $_.pm => $_";
  170 #   # generate a full package name from the base name
  171 #   unless (/::/) {
  172 #     $_ = "WeBWorK::PG::$_";
  173 #   }
  174     # import symbols from the extra package
  175     import $_;
  176     warn "Failed to evaluate module $_: $@" if $@;
  177     # record this in the appropriate place
  178     push @{$self->{ra_included_modules}}, "\%${_}::";
  179   }
  180 }
  181 
  182 =head2  new
  183   Creates the translator object.
  184 
  185 =cut
  186 
  187 
  188 sub new {
  189   my $class = shift;
  190   my $safe_cmpt = new Safe; #('PG_priv');
  191   my $self = {
  192     envir                     => undef,
  193     PG_PROBLEM_TEXT_ARRAY_REF => [],
  194     PG_PROBLEM_TEXT_REF       => 0,
  195     PG_HEADER_TEXT_REF        => 0,
  196     PG_ANSWER_HASH_REF        => {},
  197     PG_FLAGS_REF              => {},
  198     safe                      => $safe_cmpt,
  199     safe_compartment_name     => $safe_cmpt->root,
  200     errors                    => "",
  201     source                    => "",
  202     rh_correct_answers        => {},
  203     rh_student_answers        => {},
  204     rh_evaluated_answers      => {},
  205     rh_problem_result         => {},
  206     rh_problem_state          => {
  207       recorded_score       => 0, # the score recorded in the data base
  208       num_of_correct_ans   => 0, # the number of correct attempts at doing the problem
  209       num_of_incorrect_ans => 0, # the number of incorrect attempts
  210     },
  211     rf_problem_grader         => \&std_problem_grader,
  212     rf_safety_filter          => \&safetyFilter,
  213     # ra_included_modules is now populated independantly of @class_modules:
  214     ra_included_modules       => [], # [ @class_modules ],
  215     rh_directories            => {},
  216   };
  217   bless $self, $class;
  218 }
  219 
  220 =pod
  221 
  222 (b) The following routines defined within the PG module are shared:
  223 
  224   &be_strict
  225   &read_whole_problem_file
  226   &convertPath
  227   &surePathToTmpFile
  228   &fileFromPath
  229   &directoryFromPath
  230   &createFile
  231 
  232   &includePGtext
  233 
  234   &PG_answer_eval
  235   &PG_restricted_eval
  236 
  237   &send_mail_to
  238   &PGsort
  239 
  240 In addition the environment hash C<%envir> is shared.  This variable is unpacked
  241 when PG.pl is run and provides most of the environment variables for each problem
  242 template.
  243 
  244 =for html
  245 
  246   <A href =
  247   "${Global::webworkDocsURL}techdescription/pglanguage/PGenvironment.html"> environment variables</A>
  248 
  249 =cut
  250 
  251 
  252 =pod
  253 
  254 (c) Sharing macros:
  255 
  256 The macros shared with the safe compartment are
  257 
  258   '&read_whole_problem_file'
  259   '&convertPath'
  260   '&surePathToTmpFile'
  261   '&fileFromPath'
  262   '&directoryFromPath'
  263   '&createFile'
  264   '&PG_answer_eval'
  265   '&PG_restricted_eval'
  266   '&be_strict'
  267   '&send_mail_to'
  268   '&PGsort'
  269   '&dumpvar'
  270   '&includePGtext'
  271 
  272 =cut
  273 
  274 # SHARE variables and routines with safe compartment
  275 my %shared_subroutine_hash = (
  276 #IO#  '&read_whole_problem_file' => 'PGtranslator', #the values are dummies.
  277 #IO#  '&convertPath'             => 'PGtranslator', # if they're dummies, why set them to
  278 #IO#  '&surePathToTmpFile'       => 'PGtranslator', # something that seems meaningful,
  279 #IO#  '&fileFromPath'            => 'PGtranslator', # instead of '1' or something?
  280 #IO#  '&directoryFromPath'       => 'PGtranslator',
  281 #IO#  '&createFile'              => 'PGtranslator',
  282   '&PG_answer_eval'          => 'PGtranslator',
  283   '&PG_restricted_eval'      => 'PGtranslator',
  284   '&be_strict'               => 'PGtranslator',
  285 #IO#  '&send_mail_to'            => 'PGtranslator',
  286   '&PGsort'                  => 'PGtranslator',
  287   '&dumpvar'                 => 'PGtranslator',
  288 #IO#  '&includePGtext'           => 'PGtranslator',
  289 );
  290 
  291 sub initialize {
  292     my $self = shift;
  293     my $safe_cmpt = $self->{safe};
  294     #print "initializing safeCompartment",$safe_cmpt -> root(), "\n";
  295 
  296     $safe_cmpt -> share(keys %shared_subroutine_hash);
  297     no strict;
  298     local(%envir) = %{ $self ->{envir} };
  299   $safe_cmpt -> share('%envir');
  300   #local($rf_answer_eval) = sub { $self->PG_answer_eval(@_); };
  301   #local($rf_restricted_eval) = sub { $self->PG_restricted_eval(@_); };
  302   #$safe_cmpt -> share('$rf_answer_eval');
  303   #$safe_cmpt -> share('$rf_restricted_eval');
  304   use strict;
  305 
  306   # ra_included_modules is now populated independantly of @class_modules:
  307   #$self->{ra_included_modules} = [@class_modules];
  308 
  309   $safe_cmpt -> share_from('main', $self->{ra_included_modules} );
  310     # the above line will get changed when we fix the PG modules thing. heh heh.
  311 }
  312 
  313 sub environment{
  314   my $self = shift;
  315   my $envirref = shift;
  316   if ( defined($envirref) )  {
  317     if (ref($envirref) eq 'HASH') {
  318       %{ $self -> {envir} } = %$envirref;
  319     } else {
  320       $self ->{errors} .= "ERROR: The environment method for PG_translate objects requires a reference to a hash";
  321     }
  322   }
  323   $self->{envir} ; #reference to current environment
  324 }
  325 
  326 =head2   Safe compartment pass through macros
  327 
  328 
  329 
  330 =cut
  331 
  332 sub mask {
  333   my $self = shift;
  334   my $mask = shift;
  335   my $safe_compartment = $self->{safe};
  336   $safe_compartment->mask($mask);
  337 }
  338 sub permit {
  339   my $self = shift;
  340   my @array = shift;
  341   my $safe_compartment = $self->{safe};
  342   $safe_compartment->permit(@array);
  343 }
  344 sub deny {
  345 
  346   my $self = shift;
  347   my @array = shift;
  348   my $safe_compartment = $self->{safe};
  349   $safe_compartment->deny(@array);
  350 }
  351 sub share_from {
  352   my $self = shift;
  353   my $pckg_name = shift;
  354   my $array_ref =shift;
  355   my $safe_compartment = $self->{safe};
  356   $safe_compartment->share_from($pckg_name,$array_ref);
  357 }
  358 
  359 sub source_string {
  360   my $self = shift;
  361   my $temp = shift;
  362   my $out;
  363   if ( ref($temp) eq 'SCALAR') {
  364     $self->{source} = $$temp;
  365     $out = $self->{source};
  366   } elsif ($temp) {
  367     $self->{source} = $temp;
  368     $out = $self->{source};
  369   }
  370   $self -> {source};
  371 }
  372 
  373 sub source_file {
  374   my $self = shift;
  375   my $filePath = shift;
  376   local(*SOURCEFILE);
  377   local($/);
  378   $/ = undef;   # allows us to treat the file as a single line
  379   my $err = "";
  380   if ( open(SOURCEFILE, "<$filePath") ) {
  381     $self -> {source} = <SOURCEFILE>;
  382     close(SOURCEFILE);
  383   } else {
  384     $self->{errors} .= "Can't open file: $filePath";
  385     croak( "Can't open file: $filePath\n" );
  386   }
  387 
  388 
  389 
  390   $err;
  391 }
  392 
  393 
  394 
  395 sub unrestricted_load {
  396   my $self = shift;
  397   my $filePath = shift;
  398   my $safe_cmpt = $self ->{safe};
  399   my $store_mask = $safe_cmpt->mask();
  400   $safe_cmpt->mask(Opcode::empty_opset());
  401   my $safe_cmpt_package_name = $safe_cmpt->root();
  402 
  403   my $macro_file_name = fileFromPath($filePath);
  404   $macro_file_name =~s/\.pl//;  # trim off the extenstion
  405   my $export_subroutine_name = "_${macro_file_name}_export";
  406   my $init_subroutine_name = "_${macro_file_name}_init";
  407   my $macro_file_loaded;
  408   my $local_errors = "";
  409   no strict;
  410   $macro_file_loaded  = defined(&{"${safe_cmpt_package_name}::$init_subroutine_name"} );
  411   #print STDERR "$macro_file_name   has not yet been loaded\n" unless $macro_file_loaded;
  412   unless ($macro_file_loaded) {
  413     ## load the $filePath file
  414     ## Using rdo insures that the $filePath file is loaded for every problem, allowing initializations to occur.
  415     ## Ordinary mortals should not be fooling with the fundamental macros in these files.
  416     my $local_errors = "";
  417     if (-r $filePath ) {
  418       my $rdoResult = $safe_cmpt->rdo($filePath);
  419       #warn "There were problems compiling the file: $filePath: <BR>--$@" if $@;
  420       $local_errors ="\nThere were problems compiling the file:\n $filePath\n $@\n" if $@;
  421       $self ->{errors} .= $local_errors if $local_errors;
  422       use strict;
  423     } else {
  424       $local_errors = "Can't open file $filePath for reading\n";
  425       $self ->{errors} .= $local_errors if $local_errors;
  426     }
  427     $safe_cmpt -> mask($store_mask);
  428 
  429   }
  430   $macro_file_loaded  = defined(&{"${safe_cmpt_package_name}::$init_subroutine_name"} );
  431   $local_errors .= "\nUnknown error.  Unable to load $filePath\n" if ($local_errors eq '' and not $macro_file_loaded);
  432   #print STDERR "$filePath is properly loaded\n\n" if $macro_file_loaded;
  433   $local_errors;
  434 }
  435 
  436 sub nameSpace {
  437   my $self = shift;
  438   $self->{safe}->root;
  439 }
  440 
  441 sub a_text {
  442   my $self  = shift;
  443   @{$self->{PG_PROBLEM_TEXT_ARRAY_REF}};
  444 }
  445 
  446 sub header {
  447   my $self = shift;
  448   ${$self->{PG_HEADER_TEXT_REF}};
  449 }
  450 
  451 sub h_flags {
  452   my $self = shift;
  453   %{$self->{PG_FLAGS_REF}};
  454 }
  455 
  456 sub rh_flags {
  457   my $self = shift;
  458   $self->{PG_FLAGS_REF};
  459 }
  460 sub h_answers{
  461   my $self = shift;
  462   %{$self->{PG_ANSWER_HASH_REF}};
  463 }
  464 
  465 sub ra_text {
  466   my $self  = shift;
  467     $self->{PG_PROBLEM_TEXT_ARRAY_REF};
  468 
  469 }
  470 
  471 sub r_text {
  472   my $self  = shift;
  473     $self->{PG_PROBLEM_TEXT_REF};
  474 }
  475 
  476 sub r_header {
  477   my $self = shift;
  478   $self->{PG_HEADER_TEXT_REF};
  479 }
  480 
  481 sub rh_directories {
  482   my $self = shift;
  483   my $rh_directories = shift;
  484   $self->{rh_directories}=$rh_directories if ref($rh_directories) eq 'HASH';
  485   $self->{rh_directories};
  486 }
  487 
  488 sub rh_correct_answers {
  489   my $self = shift;
  490   my @in = @_;
  491   return $self->{rh_correct_answers} if @in == 0;
  492 
  493   if ( ref($in[0]) eq 'HASH' ) {
  494     $self->{rh_correct_answers} = { %{ $in[0] } }; # store a copy of the hash
  495   } else {
  496     $self->{rh_correct_answers} = { @in }; # store a copy of the hash
  497   }
  498   $self->{rh_correct_answers}
  499 }
  500 
  501 sub rf_problem_grader {
  502   my $self = shift;
  503   my $in = shift;
  504   return $self->{rf_problem_grader} unless defined($in);
  505   if (ref($in) =~/CODE/ ) {
  506     $self->{rf_problem_grader} = $in;
  507   } else {
  508     die "ERROR: Attempted to install a problem grader which was not a reference to a subroutine.";
  509   }
  510   $self->{rf_problem_grader}
  511 }
  512 
  513 
  514 sub errors{
  515   my $self = shift;
  516   $self->{errors};
  517 }
  518 
  519 # sub DESTROY {
  520 #     my $self = shift;
  521 #     my $nameSpace = $self->nameSpace;
  522 #   no strict 'refs';
  523 #     my $nm = "${nameSpace}::";
  524 #      my $nsp = \%{"$nm"};
  525 #       my @list = keys %$nsp;
  526 #       while (@list) {
  527 #       my $name = pop(@list);
  528 #       if  ( defined(&{$nsp->{$name}})  )  {
  529 #          #print "checking \&$name\n";
  530 #          unless (exists( $shared_subroutine_hash{"\&$name"} ) ) {
  531 #           undef( &{$nsp->{$name}} );
  532 #           #print "destroying \&$name\n";
  533 #          } else {
  534 #             #delete( $nsp->{$name} );
  535 #             #print "what is left",join(" ",%$nsp) ,"\n\n";
  536 #          }
  537 #
  538 #       }
  539 #       if  ( defined(${$nsp->{$name}})  )  {
  540 #          #undef( ${$nsp->{$name}} );         ## unless commented out download hardcopy bombs with Perl 5.6
  541 #            #print "destroying \$$name\n";
  542 #       }
  543 #       if  ( defined(@{$nsp->{$name}})  )  {
  544 #          undef( @{$nsp->{$name}} );
  545 #          #print "destroying \@$name\n";
  546 #       }
  547 #       if  ( defined(%{$nsp->{$name}})  )  {
  548 #          undef( %{$nsp->{$name}} ) unless $name =~ /::/ ;
  549 #          #print "destroying \%$name\n";
  550 #       }
  551 #       # changed for Perl 5.6
  552 #     delete ( $nsp->{$name} ) if defined($nsp->{$name});  # this must be uncommented in Perl 5.6 to reinitialize variables
  553 #     # changed for Perl 5.6
  554 #    #print "deleting $name\n";
  555 #     #undef( @{$nsp->{$name}} ) if defined(@{$nsp->{$name}});
  556 #     #undef( %{$nsp->{$name}} ) if defined(%{$nsp->{$name}}) and $name ne "main::";
  557 #    }
  558 #
  559 #   use strict;
  560 #     #print "\nObject going bye-bye\n";
  561 #
  562 # }
  563 
  564 =head2  set_mask
  565 
  566 
  567 
  568 
  569 
  570 
  571 (e) Now we close the safe compartment.  Only the certain operations can be used
  572 within PG problems and the PG macro files.  These include the subroutines
  573 shared with the safe compartment as defined above and most Perl commands which
  574 do not involve file access, access to the system or evaluation.
  575 
  576 Specifically the following are allowed
  577 
  578   time()
  579     # gives the current Unix time
  580     # used to determine whether solutions are visible.
  581   atan, sin cos exp log sqrt
  582     # arithemetic commands -- more are defined in PGauxiliaryFunctions.pl
  583 
  584 The following are specifically not allowed:
  585 
  586   eval()
  587   unlink, symlink, system, exec
  588   print require
  589 
  590 
  591 
  592 =cut
  593 
  594 ##############################################################################
  595 
  596           ## restrict the operations allowed within the safe compartment
  597 
  598 sub set_mask {
  599   my $self = shift;
  600   my $safe_cmpt = $self ->{safe};
  601     $safe_cmpt->mask(Opcode::full_opset());  # allow no operations
  602     $safe_cmpt->permit(qw(   :default ));
  603     $safe_cmpt->permit(qw(time));  # used to determine whether solutions are visible.
  604   $safe_cmpt->permit(qw( atan2 sin cos exp log sqrt ));
  605 
  606   # just to make sure we'll deny some things specifically
  607   $safe_cmpt->deny(qw(entereval));
  608   $safe_cmpt->deny(qw (  unlink symlink system exec ));
  609   $safe_cmpt->deny(qw(print require));
  610 }
  611 
  612 ############################################################################
  613 
  614 
  615 =head2  Translate
  616 
  617 
  618 =cut
  619 
  620 sub translate {
  621   my $self = shift;
  622   my @PROBLEM_TEXT_OUTPUT = ();
  623   my $safe_cmpt = $self ->{safe};
  624   my $evalString = $self -> {source};
  625   $self ->{errors} .= qq{ERROR:  This problem file was empty!\n} unless ($evalString) ;
  626   $self ->{errors} .= qq{ERROR:  You must define the environment before translating.}
  627        unless defined( $self->{envir} );
  628     # reset the error detection
  629     my $save_SIG_die_trap = $SIG{__DIE__};
  630     $SIG{__DIE__} = sub {CORE::die(@_) };
  631 
  632 
  633 
  634 =pod
  635 
  636 (3) B<Preprocess the problem text>
  637 
  638 The input text is subjected to two global replacements.
  639 First every incidence of
  640 
  641   BEGIN_TEXT
  642   problem text
  643   END_TEXT
  644 
  645 is replaced by
  646 
  647     TEXT( EV3( <<'END_TEXT' ) );
  648   problem text
  649   END_TEXT
  650 
  651 The first construction is syntactic sugar for the second. This is explained
  652 in C<PGbasicmacros.pl>.
  653 
  654 Second every incidence
  655 of \ (backslash) is replaced by \\ (double backslash).  Third each incidence of
  656 ~~ is replaced by a single backslash.
  657 
  658 This is done to alleviate a basic
  659 incompatibility between TeX and Perl. TeX uses backslashes constantly to denote
  660 a command word (as opposed to text which is to be entered literally).  Perl
  661 uses backslash to escape the following symbol.  This escape
  662 mechanism takes place immediately when a Perl script is compiled and takes
  663 place throughout the code and within every quoted string (both double and single
  664 quoted strings) with the single exception of single quoted "here" documents.
  665 That is backlashes which appear in
  666 
  667     TEXT(<<'EOF');
  668     ... text including \{   \} for example
  669     EOF
  670 
  671 are the only ones not immediately evaluated.  This behavior makes it very difficult
  672 to use TeX notation for defining mathematics within text.
  673 
  674 The initial global
  675 replacement, before compiling a PG problem, allows one to use backslashes within
  676 text without doubling them. (The anomolous behavior inside single quoted "here"
  677 documents is compensated for by the behavior of the evaluation macro EV3.) This
  678 makes typing TeX easy, but introduces one difficulty in entering normal Perl code.
  679 
  680 The second global replacement provides a work around for this -- use ~~ when you
  681 would ordinarily use a backslash in Perl code.
  682 In order to define a carriage return use ~~n rather than \n; in order to define
  683 a reference to a variable you must use ~~@array rather than \@array. This is
  684 annoying and a source of simple compiler errors, but must be lived with.
  685 
  686 The problems are not evaluated in strict mode, so global variables can be used
  687 without warnings.
  688 
  689 
  690 
  691 =cut
  692 
  693 ############################################################################
  694 
  695 
  696         ##########################################
  697         ###### PG preprocessing code #############
  698         ##########################################
  699             # BEGIN_TEXT and END_TEXT must occur on a line by themselves.
  700             $evalString =~ s/\n\s*END_TEXT[\s;]*\n/\nEND_TEXT\n/g;
  701           $evalString =~ s/\n\s*BEGIN_TEXT[\s;]*\n/\nTEXT\(EV3\(<<'END_TEXT'\)\);\n/g;
  702           $evalString =~ s/ENDDOCUMENT.*/ENDDOCUMENT();/s; # remove text after ENDDOCUMENT
  703 
  704         $evalString =~ s/\\/\\\\/g;    # \ can't be used for escapes because of TeX conflict
  705             $evalString =~ s/~~/\\/g;      # use ~~ as escape instead, use # for comments
  706 
  707 =pod
  708 
  709 (4) B<Evaluate the problem text>
  710 
  711 Evaluate the text within the safe compartment.  Save the errors. The safe
  712 compartment is a new one unless the $safeCompartment was set to zero in which
  713 case the previously defined safe compartment is used. (See item 1.)
  714 
  715 =cut
  716 
  717 
  718         my ($PG_PROBLEM_TEXT_REF, $PG_HEADER_TEXT_REF, $PG_ANSWER_HASH_REF, $PG_FLAGS_REF)
  719               =$safe_cmpt->reval("   $evalString");
  720 
  721 # This section could use some more error messages.  In particular if a problem doesn't produce the right output, the user needs
  722 # information about which problem was at fault.
  723 #
  724 #
  725 
  726         $self->{errors} .= $@;
  727 #         push(@PROBLEM_TEXT_OUTPUT   ,   split(/(\n)/,$$PG_PROBLEM_TEXT_REF)  ) if  defined($$PG_PROBLEM_TEXT_REF  );
  728           push(@PROBLEM_TEXT_OUTPUT   ,   split(/^/,$$PG_PROBLEM_TEXT_REF)  ) if  ref($PG_PROBLEM_TEXT_REF  ) eq 'SCALAR';
  729                                                                            ## This is better than using defined($$PG_PROBLEM_TEXT_REF)
  730                                                                            ## Because more pleasant feedback is given
  731                                                                            ## when the problem doesn't render.
  732            # try to get the \n to appear at the end of the line
  733 
  734         use strict;
  735         #############################################################################
  736         ##########  end  EVALUATION code                                  ###########
  737         #############################################################################
  738 
  739 =pod
  740 
  741 (5) B<Process errors>
  742 
  743 The error provided by Perl
  744 is truncated slightly and returned. In the text
  745 string which would normally contain the rendered problem.
  746 
  747 The original text string is given line numbers and concatenated to
  748 the errors.
  749 
  750 =cut
  751 
  752 
  753 
  754         ##########################################
  755     ###### PG error processing code ##########
  756     ##########################################
  757         my (@input,$lineNumber,$line);
  758         if ($self -> {errors}) {
  759                 #($self -> {errors}) =~ s/</&lt/g;
  760                 #($self -> {errors}) =~ s/>/&gt/g;
  761            #try to clean up errors so they will look ok
  762                 $self ->{errors} =~ s/\[.+?\.pl://gm;   #erase [Fri Dec 31 12:58:30 1999] processProblem7.pl:
  763                 #$self -> {errors} =~ s/eval\s+'(.|[\n|r])*$//;
  764             #end trying to clean up errors so they will look ok
  765 
  766 
  767                 push(@PROBLEM_TEXT_OUTPUT   ,  qq!\n<A NAME="problem! .
  768                     $self->{envir} ->{'probNum'} .
  769                     qq!"><PRE>        Problem!.
  770                     $self->{envir} ->{'probNum'}.
  771                     qq!\nERROR caught by PGtranslator while processing problem file:! .
  772                   $self->{envir}->{'probFileName'}.
  773                   "\n****************\r\n" .
  774                   $self -> {errors}."\r\n" .
  775                   "****************<BR>\n");
  776 
  777                 push(@PROBLEM_TEXT_OUTPUT   , "------Input Read\r\n");
  778                $self->{source} =~ s/</&lt;/g;
  779                @input=split("\n", $self->{source});
  780                $lineNumber = 1;
  781                 foreach $line (@input) {
  782                     chomp($line);
  783                     push(@PROBLEM_TEXT_OUTPUT, "$lineNumber\t\t$line\r\n");
  784                     $lineNumber ++;
  785                 }
  786                 push(@PROBLEM_TEXT_OUTPUT  ,"\n-----<BR></PRE>\r\n");
  787 
  788 
  789 
  790         }
  791 
  792 =pod
  793 
  794 (6) B<Prepare return values>
  795 
  796   Returns:
  797       $PG_PROBLEM_TEXT_ARRAY_REF -- Reference to a string containing the rendered text.
  798       $PG_HEADER_TEXT_REF -- Reference to a string containing material to placed in the header (for use by JavaScript)
  799       $PG_ANSWER_HASH_REF -- Reference to an array containing the answer evaluators.
  800       $PG_FLAGS_REF -- Reference to a hash containing flags and other references:
  801         'error_flag' is set to 1 if there were errors in rendering
  802 
  803 =cut
  804 
  805         ## we need to make sure that the other output variables are defined
  806 
  807                 ## If the eval failed with errors, one or more of these variables won't be defined.
  808                 $PG_ANSWER_HASH_REF = {}      unless defined($PG_ANSWER_HASH_REF);
  809                 $PG_HEADER_TEXT_REF = \( "" ) unless defined($PG_HEADER_TEXT_REF);
  810                 $PG_FLAGS_REF = {}            unless defined($PG_FLAGS_REF);
  811 
  812             $PG_FLAGS_REF->{'error_flag'} = 1     if $self -> {errors};
  813         my $PG_PROBLEM_TEXT                     = join("",@PROBLEM_TEXT_OUTPUT);
  814 
  815         $self ->{ PG_PROBLEM_TEXT_REF }     = \$PG_PROBLEM_TEXT;
  816         $self ->{ PG_PROBLEM_TEXT_ARRAY_REF }   = \@PROBLEM_TEXT_OUTPUT;
  817       $self ->{ PG_HEADER_TEXT_REF  }   = $PG_HEADER_TEXT_REF;
  818       $self ->{ rh_correct_answers  }   = $PG_ANSWER_HASH_REF;
  819       $self ->{ PG_FLAGS_REF      }   = $PG_FLAGS_REF;
  820       $SIG{__DIE__} = $save_SIG_die_trap;
  821       $self ->{errors};
  822 }  # end translate
  823 
  824 
  825 =head2   Answer evaluation methods
  826 
  827 =cut
  828 
  829 =head3  access methods
  830 
  831   $obj->rh_student_answers
  832 
  833 =cut
  834 
  835 
  836 
  837 sub rh_evaluated_answers {
  838   my $self = shift;
  839   my @in = @_;
  840   return $self->{rh_evaluated_answers} if @in == 0;
  841 
  842   if ( ref($in[0]) eq 'HASH' ) {
  843     $self->{rh_evaluated_answers} = { %{ $in[0] } }; # store a copy of the hash
  844   } else {
  845     $self->{rh_evaluated_answers} = { @in }; # store a copy of the hash
  846   }
  847   $self->{rh_evaluated_answers};
  848 }
  849 sub rh_problem_result {
  850   my $self = shift;
  851   my @in = @_;
  852   return $self->{rh_problem_result} if @in == 0;
  853 
  854   if ( ref($in[0]) eq 'HASH' ) {
  855     $self->{rh_problem_result} = { %{ $in[0] } }; # store a copy of the hash
  856   } else {
  857     $self->{rh_problem_result} = { @in }; # store a copy of the hash
  858   }
  859   $self->{rh_problem_result};
  860 }
  861 sub rh_problem_state {
  862   my $self = shift;
  863   my @in = @_;
  864   return $self->{rh_problem_state} if @in == 0;
  865 
  866   if ( ref($in[0]) eq 'HASH' ) {
  867     $self->{rh_problem_state} = { %{ $in[0] } }; # store a copy of the hash
  868   } else {
  869     $self->{rh_problem_state} = { @in }; # store a copy of the hash
  870   }
  871   $self->{rh_problem_state};
  872 }
  873 
  874 
  875 =head3 process_answers
  876 
  877 
  878   $obj->process_answers()
  879 
  880 
  881 =cut
  882 
  883 
  884 sub process_answers{
  885   my $self = shift;
  886   my @in = @_;
  887   my %h_student_answers;
  888   if (ref($in[0]) eq 'HASH' ) {
  889     %h_student_answers = %{ $in[0] };  #receiving a reference to a hash of answers
  890   } else {
  891     %h_student_answers = @in;          # receiving a hash of answers
  892   }
  893   my $rh_correct_answers = $self->rh_correct_answers();
  894   my @answer_entry_order = ( defined($self->{PG_FLAGS_REF}->{ANSWER_ENTRY_ORDER}) ) ?
  895                         @{$self->{PG_FLAGS_REF}->{ANSWER_ENTRY_ORDER}} : keys %{$rh_correct_answers};
  896 
  897   # apply each instructors answer to the corresponding student answer
  898 
  899   foreach my $ans_name ( @answer_entry_order ) {
  900     my ($ans, $errors) = $self->filter_answer( $h_student_answers{$ans_name} );
  901     no strict;
  902     # evaluate the answers inside the safe compartment.
  903     local($rf_fun,$temp_ans) = (undef,undef);
  904     if ( defined($rh_correct_answers ->{$ans_name} ) ) {
  905       $rf_fun  = $rh_correct_answers->{$ans_name};
  906     } else {
  907       warn "There is no answer evaluator for the question labeled $ans_name";
  908     }
  909     $temp_ans  = $ans;
  910     $temp_ans = '' unless defined($temp_ans); #make sure that answer is always defined
  911                                               # in case the answer evaluator forgets to check
  912     $self->{safe}->share('$rf_fun','$temp_ans');
  913 
  914     # reset the error detection
  915     my $save_SIG_die_trap = $SIG{__DIE__};
  916     $SIG{__DIE__} = sub {CORE::die(@_) };
  917     my $rh_ans_evaluation_result;
  918     if (ref($rf_fun) eq 'CODE' ) {
  919       $rh_ans_evaluation_result = $self->{safe} ->reval( '&{ $rf_fun }($temp_ans)' ) ;
  920       warn "Error in PGtranslator.pm::process_answers: Answer $ans_name:<BR>\n $@\n" if $@;
  921     } elsif (ref($rf_fun) eq 'AnswerEvaluator')   {
  922       $rh_ans_evaluation_result = $self->{safe} ->reval('$rf_fun->evaluate($temp_ans)');
  923       warn "Error in PGtranslator.pm::process_answers: Answer $ans_name:<BR>\n $@\n" if $@;
  924       warn "Evaluation error: Answer $ans_name:<BR>\n",
  925         $rh_ans_evaluation_result->error_flag(), " :: ",
  926         $rh_ans_evaluation_result->error_message(),"<BR>\n"
  927           if defined($rh_ans_evaluation_result)
  928             and defined($rh_ans_evaluation_result->error_flag());
  929     } else {
  930       warn "Error in PGtranslator5.pm::process_answers: Answer $ans_name:<BR>\n Unrecognized evaluator type |", ref($rf_fun), "|";
  931     }
  932 
  933     $SIG{__DIE__} = $save_SIG_die_trap;
  934 
  935 
  936     use strict;
  937     unless ( ( ref($rh_ans_evaluation_result) eq 'HASH') or ( ref($rh_ans_evaluation_result) eq 'AnswerHash') ) {
  938       warn "Error in PGtranslator5.pm::process_answers: Answer $ans_name:<BR>\n
  939         Answer evaluators must return a hash or an AnswerHash type, not type |",
  940         ref($rh_ans_evaluation_result), "|";
  941     }
  942     $rh_ans_evaluation_result ->{ans_message} .= "$errors \n" if $errors;
  943     $rh_ans_evaluation_result ->{ans_name} = $ans_name;
  944     $self->{rh_evaluated_answers}->{$ans_name} = $rh_ans_evaluation_result;
  945   }
  946   $self->rh_evaluated_answers;
  947 }
  948 
  949 
  950 
  951 =head3 grade_problem
  952 
  953   $obj->rh_problem_state(%problem_state);  # sets the current problem state
  954   $obj->grade_problem(%form_options);
  955 
  956 
  957 =cut
  958 
  959 
  960 sub grade_problem {
  961   my $self = shift;
  962     my %form_options = @_;
  963   my $rf_grader = $self->{rf_problem_grader};
  964   ($self->{rh_problem_result},$self->{rh_problem_state} )  =
  965                     &{$rf_grader}(  $self -> {rh_evaluated_answers},
  966                                     $self -> {rh_problem_state},
  967                                     %form_options
  968                                   );
  969 
  970   ($self->{rh_problem_result}, $self->{rh_problem_state} ) ;
  971 }
  972 
  973 sub rf_std_problem_grader {
  974     my $self = shift;
  975   return \&std_problem_grader;
  976 }
  977 sub old_std_problem_grader{
  978   my $rh_evaluated_answers = shift;
  979   my %flags = @_;  # not doing anything with these yet
  980   my %evaluated_answers = %{$rh_evaluated_answers};
  981   my  $allAnswersCorrectQ=1;
  982   foreach my $ans_name (keys %evaluated_answers) {
  983   # I'm not sure if this check is really useful.
  984       if (ref($evaluated_answers{$ans_name} ) eq 'HASH' ) {
  985         $allAnswersCorrectQ = 0 unless( 1 == $evaluated_answers{$ans_name}->{score} );
  986       } else {
  987         warn "Error: Answer $ans_name is not a hash";
  988         warn "$evaluated_answers{$ans_name}";
  989       }
  990   }
  991   # Notice that "all answers are correct" if there are no questions.
  992   { score       => $allAnswersCorrectQ,
  993     prev_tries    => 0,
  994     partial_credit  => $allAnswersCorrectQ,
  995     errors      =>  "",
  996     type              => 'old_std_problem_grader',
  997     flags       => {}, # not doing anything with these yet
  998   };  # hash output
  999 
 1000 }
 1001 
 1002 #####################################
 1003 # This is a model for plug-in problem graders
 1004 #####################################
 1005 
 1006 sub std_problem_grader{
 1007   my $rh_evaluated_answers = shift;
 1008   my $rh_problem_state = shift;
 1009   my %form_options = @_;
 1010   my %evaluated_answers = %{$rh_evaluated_answers};
 1011   #  The hash $rh_evaluated_answers typically contains:
 1012   #      'answer1' => 34, 'answer2'=> 'Mozart', etc.
 1013 
 1014   # By default the  old problem state is simply passed back out again.
 1015   my %problem_state = %$rh_problem_state;
 1016 
 1017 
 1018   # %form_options might include
 1019   # The user login name
 1020   # The permission level of the user
 1021   # The studentLogin name for this psvn.
 1022   # Whether the form is asking for a refresh or is submitting a new answer.
 1023 
 1024   # initial setup of the answer
 1025   my %problem_result = ( score        => 0,
 1026                errors         => '',
 1027                type           => 'std_problem_grader',
 1028                msg          => '',
 1029              );
 1030   # Checks
 1031 
 1032   my $ansCount = keys %evaluated_answers;  # get the number of answers
 1033   unless ($ansCount > 0 ) {
 1034     $problem_result{msg} = "This problem did not ask any questions.";
 1035     return(\%problem_result,\%problem_state);
 1036   }
 1037 
 1038   if ($ansCount > 1 ) {
 1039     $problem_result{msg} = 'In order to get credit for this problem all answers must be correct.' ;
 1040   }
 1041 
 1042   unless (defined( $form_options{answers_submitted}) and $form_options{answers_submitted} == 1) {
 1043     return(\%problem_result,\%problem_state);
 1044   }
 1045 
 1046   my  $allAnswersCorrectQ=1;
 1047   foreach my $ans_name (keys %evaluated_answers) {
 1048   # I'm not sure if this check is really useful.
 1049       if ( ( ref($evaluated_answers{$ans_name} ) eq 'HASH' ) or ( ref($evaluated_answers{$ans_name}) eq 'AnswerHash' ) ) {
 1050         $allAnswersCorrectQ = 0 unless( 1 == $evaluated_answers{$ans_name}->{score} );
 1051       } else {
 1052         warn "Error: Answer $ans_name is not a hash";
 1053         warn "$evaluated_answers{$ans_name}";
 1054         warn "This probably means that the answer evaluator is for this answer is not working correctly.";
 1055         $problem_result{error} = "Error: Answer $ans_name is not a hash: $evaluated_answers{$ans_name}";
 1056       }
 1057   }
 1058   # report the results
 1059   $problem_result{score} = $allAnswersCorrectQ;
 1060 
 1061   # I don't like to put in this bit of code.
 1062   # It makes it hard to construct error free problem graders
 1063   # I would prefer to know that the problem score was numeric.
 1064     unless ($problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) {
 1065       $problem_state{recorded_score} = 0;  # This gets rid of non-numeric scores
 1066     }
 1067     #
 1068   if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) {
 1069     $problem_state{recorded_score} = 1;
 1070   } else {
 1071     $problem_state{recorded_score} = 0;
 1072   }
 1073 
 1074   $problem_state{num_of_correct_ans}++ if $allAnswersCorrectQ == 1;
 1075   $problem_state{num_of_incorrect_ans}++ if $allAnswersCorrectQ == 0;
 1076   (\%problem_result, \%problem_state);
 1077 }
 1078 sub rf_avg_problem_grader {
 1079     my $self = shift;
 1080   return \&avg_problem_grader;
 1081 }
 1082 sub avg_problem_grader{
 1083   my $rh_evaluated_answers = shift;
 1084   my $rh_problem_state = shift;
 1085   my %form_options = @_;
 1086   my %evaluated_answers = %{$rh_evaluated_answers};
 1087   #  The hash $rh_evaluated_answers typically contains:
 1088   #      'answer1' => 34, 'answer2'=> 'Mozart', etc.
 1089 
 1090   # By default the  old problem state is simply passed back out again.
 1091   my %problem_state = %$rh_problem_state;
 1092 
 1093 
 1094   # %form_options might include
 1095   # The user login name
 1096   # The permission level of the user
 1097   # The studentLogin name for this psvn.
 1098   # Whether the form is asking for a refresh or is submitting a new answer.
 1099 
 1100   # initial setup of the answer
 1101   my  $total=0;
 1102   my %problem_result = (
 1103     score => 0,
 1104     errors => '',
 1105     type => 'avg_problem_grader',
 1106     msg => '',
 1107   );
 1108   my $count = keys %evaluated_answers;
 1109   $problem_result{msg} = 'You can earn partial credit on this problem.' if $count >1;
 1110   # Return unless answers have been submitted
 1111   unless ($form_options{answers_submitted} == 1) {
 1112     return(\%problem_result,\%problem_state);
 1113   }
 1114   # Answers have been submitted -- process them.
 1115   foreach my $ans_name (keys %evaluated_answers) {
 1116     $total += $evaluated_answers{$ans_name}->{score};
 1117   }
 1118   # Calculate score rounded to three places to avoid roundoff problems
 1119   $problem_result{score} = $total/$count if $count;
 1120   # increase recorded score if the current score is greater.
 1121   $problem_state{recorded_score} = $problem_result{score} if $problem_result{score} > $problem_state{recorded_score};
 1122 
 1123 
 1124   $problem_state{num_of_correct_ans}++ if $total == $count;
 1125   $problem_state{num_of_incorrect_ans}++ if $total < $count ;
 1126   warn "Error in grading this problem the total $total is larger than $count" if $total > $count;
 1127   (\%problem_result, \%problem_state);
 1128 
 1129 }
 1130 =head3 safetyFilter
 1131 
 1132   ($filtered_ans, $errors) = $obj ->filter_ans($ans)
 1133                                $obj ->rf_safety_filter()
 1134 
 1135 =cut
 1136 
 1137 sub filter_answer {
 1138   my $self = shift;
 1139   my $ans = shift;
 1140   my @filtered_answers;
 1141   my $errors='';
 1142   if (ref($ans) eq 'ARRAY') {   #handle the case where the answer comes from several inputs with the same name
 1143                   # In many cases this will be passed as a reference to an array
 1144                   # if it is passed as a single string (separated by \0 characters) as
 1145                   # some early versions of CGI behave, then
 1146                   # it is unclear what will happen when the answer is filtered.
 1147     foreach my $item (@{$ans}) {
 1148       my ($filtered_ans, $error) = &{ $self->{rf_safety_filter} } ($item);
 1149       push(@filtered_answers, $filtered_ans);
 1150       $errors .= " ". $error if $error;  # add error message if error is non-zero.
 1151     }
 1152     (\@filtered_answers,$errors);
 1153 
 1154   } else {
 1155     &{ $self->{rf_safety_filter} } ($ans);
 1156   }
 1157 
 1158 }
 1159 sub rf_safety_filter {
 1160   my $self = shift;
 1161   my $rf_filter = shift;
 1162   $self->{rf_safety_filter} = $rf_filter if $rf_filter and ref($rf_filter) eq 'CODE';
 1163   warn "The safety_filter must be a reference to a subroutine" unless ref($rf_filter) eq 'CODE' ;
 1164   $self->{rf_safety_filter}
 1165 }
 1166 sub safetyFilter {
 1167       my $answer = shift;  # accepts one answer and checks it
 1168       my $submittedAnswer = $answer;
 1169     $answer = '' unless defined $answer;
 1170     my ($errorno);
 1171     $answer =~ tr/\000-\037/ /;
 1172    #### Return if answer field is empty ########
 1173     unless ($answer =~ /\S/) {
 1174 #     $errorno = "<BR>No answer was submitted.";
 1175             $errorno = 0;  ## don't report blank answer as error
 1176 
 1177       return ($answer,$errorno);
 1178       }
 1179    ######### replace ^ with **    (for exponentiation)
 1180    #  $answer =~ s/\^/**/g;
 1181    ######### Return if  forbidden characters are found
 1182     unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)]+$/ )  {
 1183       $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
 1184       $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>";
 1185 
 1186       return ($answer,$errorno);
 1187       }
 1188 
 1189     $errorno = 0;
 1190     return($answer, $errorno);
 1191 }
 1192 
 1193 ##   Check submittedAnswer for forbidden characters, etc.
 1194 #     ($submittedAnswer,$errorno) = safetyFilter($submittedAnswer);
 1195 #       $errors .= "No answer was submitted.<BR>" if $errorno == 1;
 1196 #       $errors .= "There are forbidden characters in your answer: $submittedAnswer<BR>" if $errorno ==2;
 1197 #
 1198 ##   Check correctAnswer for forbidden characters, etc.
 1199 #     unless (ref($correctAnswer) ) {  #skip check if $correctAnswer is a function
 1200 #       ($correctAnswer,$errorno) = safetyFilter($correctAnswer);
 1201 #       $errors .= "No correct answer is given in the statement of the problem.
 1202 #                   Please report this to your instructor.<BR>" if $errorno == 1;
 1203 #       $errors .= "There are forbidden characters in the problems answer.
 1204 #                   Please report this to your instructor.<BR>" if $errorno == 2;
 1205 #     }
 1206 
 1207 
 1208 
 1209 =head2 PGsort
 1210 
 1211 Because of the way sort is optimized in Perl, the symbols $a and $b
 1212 have special significance.
 1213 
 1214 C<sort {$a<=>$b} @list>
 1215 C<sort {$a cmp $b} @list>
 1216 
 1217 sorts the list numerically and lexically respectively.
 1218 
 1219 If C<my $a;> is used in a problem, before the sort routine is defined in a macro, then
 1220 things get badly confused.  To correct this, the following macros are defined in
 1221 dangerougMacros.pl which is evaluated before the problem template is read.
 1222 
 1223   PGsort sub { $_[0] <=> $_[1] }, @list;
 1224   PGsort sub { $_[0] cmp $_[1] }, @list;
 1225 
 1226 provide slightly slower, but safer, routines for the PG language. (The subroutines
 1227 for ordering are B<required>. Note the commas!)
 1228 
 1229 =cut
 1230 # This sort can cause troubles because of its special use of $a and $b
 1231 # Putting it in dangerousMacros.pl worked frequently, but not always.
 1232 # In particular ANS( ans_eva1 ans_eval2) caused trouble.
 1233 # One answer at a time did not --- very strange.
 1234 
 1235 sub PGsort {
 1236   my $sort_order = shift;
 1237   die "Must supply an ordering function with PGsort: PGsort sub {\$a cmp \$b }, \@list\n" unless ref($sort_order) eq 'CODE';
 1238   sort {&$sort_order($a,$b)} @_;
 1239 }
 1240 
 1241 =head2 includePGtext
 1242 
 1243   includePGtext($string_ref, $envir_ref)
 1244 
 1245 Calls C<createPGtext> recursively with the $safeCompartment variable set to 0
 1246 so that the rendering continues in the current safe compartment.  The output
 1247 is the same as the output from createPGtext. This is used in processing
 1248 some of the sample CAPA files.
 1249 
 1250 =cut
 1251 
 1252 #this is a method for importing additional PG files from within one PG file.
 1253 # sub includePGtext {
 1254 #     my $self = shift;
 1255 #     my $string_ref =shift;
 1256 #     my $envir_ref = shift;
 1257 #     $self->environment($envir_ref);
 1258 #   $self->createPGtext($string_ref);
 1259 # }
 1260 # evaluation macros
 1261 
 1262 
 1263 
 1264 no strict;   # this is important -- I guess because eval operates on code which is not written with strict in mind.
 1265 
 1266 
 1267 
 1268 =head2 PG_restricted_eval
 1269 
 1270   PG_restricted_eval($string)
 1271 
 1272 Evaluated in package 'main'. Result of last statement is returned.
 1273 When called from within a safe compartment the safe compartment package
 1274 is 'main'.
 1275 
 1276 
 1277 =cut
 1278 
 1279 sub PG_restricted_eval {
 1280     my $string = shift;
 1281     my ($pck,$file,$line) = caller;
 1282     my $save_SIG_warn_trap = $SIG{__WARN__};
 1283     $SIG{__WARN__} = sub { CORE::die @_};
 1284     my $save_SIG_die_trap = $SIG{__DIE__};
 1285     $SIG{__DIE__}= sub {CORE::die @_};
 1286     no strict;
 1287     my $out = eval  ("package main; " . $string );
 1288     my $errors =$@;
 1289     my $full_error_report = "PG_restricted_eval detected error at line $line of file $file \n"
 1290                 . $errors .
 1291                 "The calling package is $pck\n" if defined($errors) && $errors =~/\S/;
 1292     use strict;
 1293     $SIG{__DIE__} = $save_SIG_die_trap;
 1294     $SIG{__WARN__} = $save_SIG_warn_trap;
 1295     return (wantarray) ?  ($out, $errors,$full_error_report) : $out;
 1296 }
 1297 
 1298 =head2 PG_answer_eval
 1299 
 1300 
 1301   PG_answer_eval($string)
 1302 
 1303 Evaluated in package defined by the current safe compartment.
 1304 Result of last statement is returned.
 1305 When called from within a safe compartment the safe compartment package
 1306 is 'main'.
 1307 
 1308 There is still some confusion about how these two evaluation subroutines work
 1309 and how best to define them.  It is useful to have two evaluation procedures
 1310 since at some point one might like to make the answer evaluations more stringent.
 1311 
 1312 =cut
 1313 
 1314 
 1315 sub PG_answer_eval {
 1316    local($string) = shift;   # I made this local just in case -- see PG_estricted_eval
 1317    my $errors = '';
 1318    my $full_error_report = '';
 1319    my ($pck,$file,$line) = caller;
 1320     # Because of the global variable $PG::compartment_name and $PG::safe_cmpt
 1321     # only one problem safe compartment can be active at a time.
 1322     # This might cause problems at some point.  In that case a cleverer way
 1323     # of insuring that the package stays in scope until the answer is evaluated
 1324     # will be required.
 1325 
 1326     # This is pretty tricky and doesn't always work right.
 1327     # We seem to need PG_priv instead of main when PG_answer_eval is called within a completion
 1328     # 'package PG_priv; '
 1329     my $save_SIG_warn_trap = $SIG{__WARN__};
 1330     $SIG{__WARN__} = sub { CORE::die @_};
 1331     my $save_SIG_die_trap = $SIG{__DIE__};
 1332     $SIG{__DIE__}= sub {CORE::die @_};
 1333     my $save_SIG_FPE_trap= $SIG{'FPE'};
 1334     #$SIG{'FPE'} = \&main::PG_floating_point_exception_handler;
 1335     #$SIG{'FPE'} = sub {exit(0)};
 1336     no strict;
 1337     my $out = eval('package main;'.$string);
 1338     $out = '' unless defined($out);
 1339     $errors .=$@;
 1340 
 1341     $full_error_report = "ERROR: at line $line of file $file
 1342                 $errors
 1343                 The calling package is $pck\n" if defined($errors) && $errors =~/\S/;
 1344     use strict;
 1345     $SIG{__DIE__} = $save_SIG_die_trap;
 1346     $SIG{__WARN__} = $save_SIG_warn_trap;
 1347     $SIG{'FPE'} = $save_SIG_FPE_trap if defined $save_SIG_FPE_trap;
 1348     return (wantarray) ?  ($out, $errors,$full_error_report) : $out;
 1349 
 1350 
 1351 }
 1352 
 1353 sub dumpvar {
 1354     my ($packageName) = @_;
 1355 
 1356     local(*alias);
 1357 
 1358     sub emit {
 1359       print @_;
 1360     }
 1361 
 1362     *stash = *{"${packageName}::"};
 1363     $, = "  ";
 1364 
 1365     emit "Content-type: text/html\n\n<PRE>\n";
 1366 
 1367 
 1368     while ( ($varName, $globValue) = each %stash) {
 1369         emit "$varName\n";
 1370 
 1371   *alias = $globValue;
 1372   next if $varName=~/main/;
 1373 
 1374   if (defined($alias) ) {
 1375       emit "  \$$varName $alias \n";
 1376   }
 1377 
 1378   if ( defined(@alias) ) {
 1379       emit "  \@$varName @alias \n";
 1380   }
 1381   if (defined(%alias) ) {
 1382       emit "  %$varName \n";
 1383       foreach $key (keys %alias) {
 1384           emit "    $key => $alias{$key}\n";
 1385       }
 1386 
 1387 
 1388 
 1389   }
 1390     }
 1391     emit "</PRE></PRE>";
 1392 
 1393 
 1394 }
 1395 use strict;
 1396 
 1397 #### for error checking and debugging purposes
 1398 sub pretty_print_rh {
 1399   my $rh = shift;
 1400   foreach my $key (sort keys %{$rh})  {
 1401     warn "  $key => ",$rh->{$key},"\n";
 1402   }
 1403 }
 1404 # end evaluation subroutines
 1405 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9