[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 1116 - (download) (as text) (annotate)
Wed Jun 11 03:59:00 2003 UTC (10 years ago) by gage
File size: 44039 byte(s)
Added a feature to process_answers.  Each answer evaluator is given the
answer AND the answer label (e.g. AnSWer1) of the answer.  The label
is placed in the answer hash at $hash{ans_label} for use by filters
that need to know the label of the answer they are evaluating.

A corresponding change has been made in AnswerEvaluator in AnswerHash.pm

--Mike

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9