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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1249 - (download) (as text) (annotate)
Mon Jun 23 14:24:13 2003 UTC (9 years, 11 months ago) by gage
File size: 19078 byte(s)
Added caching code for reading PGbascimacros and PGanswermacros as well
as PG.pl, dangerousMacros and IO.pl into a cached safe compartment and
sharing the subroutines with the current safe compartment
It speeds up each problem significantly (about .2 seconds on webwork3).

Changes are required in PGbasicmacros and PGanswermacros to fix
assumptions that are not met when a file is compiled and run in
different name spaces.

The caching code is turned off by default.  It must be turned on by
by changing commenting in lines 133 to 147 in WeBWorK::PG:Local.pm
--Mike

    1 ################################################################################
    2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
    3 # $Id$
    4 ################################################################################
    5 
    6 package WeBWorK::PG::Local;
    7 
    8 =head1 NAME
    9 
   10 WeBWorK::PG::Local - Use the WeBWorK::PG API to invoke a local
   11 WeBWorK::PG::Translator object.
   12 
   13 =head1 DESCRIPTION
   14 
   15 WeBWorK::PG::Local encapsulates the PG translation process, making multiple
   16 calls to WeBWorK::PG::Translator. Much of the flexibility of the Translator is
   17 hidden, instead making choices that are appropriate for the webwork-modperl
   18 system
   19 
   20 It implements the WeBWorK::PG interface and uses a local
   21 WeBWorK::PG::Translator to perform problem rendering. See the documentation for
   22 the WeBWorK::PG module for information about the API.
   23 
   24 =cut
   25 
   26 use strict;
   27 use warnings;
   28 use File::Path qw(rmtree);
   29 use WeBWorK::PG::ImageGenerator;
   30 use WeBWorK::PG::Translator;
   31 use WeBWorK::Utils qw(readFile formatDateTime writeTimingLogEntry makeTempDirectory);
   32 BEGIN {
   33 
   34 # This safe compartment is used to read the large macro files such as
   35 #   PG.pl, PGbasicmacros.pl and PGanswermacros and cache the results so that
   36 #   future calls have preloaded versions of these large files.
   37 #   This saves a significant amount of time.
   38 
   39   $WeBWorK::PG::Local::safeCache = new Safe;
   40 # warn "Creating new Safe cache compartment ".$WeBWorK::PG::Local::safeCache->root;
   41 }
   42 sub new {
   43   my $invocant = shift;
   44   my $class = ref($invocant) || $invocant;
   45   my (
   46     $ce,
   47     $user,
   48     $key,
   49     $set,
   50     $problem,
   51     $psvn,
   52     $formFields, # in CGI::Vars format
   53     $translationOptions, # hashref containing options for the
   54                          # translator, such as whether to show
   55              # hints and the display mode to use
   56   ) = @_;
   57 
   58   # write timing log entry
   59   writeTimingLogEntry($ce, "WeBWorK::PG::new",
   60     "user=".$user->user_id.",problem=".$ce->{courseName}."/".$set->set_id."/".$problem->problem_id.",mode=".$translationOptions->{displayMode},
   61     "begin");
   62 
   63   # install a local warn handler to collect warnings
   64   my $warnings = "";
   65   local $SIG{__WARN__} = sub { $warnings .= shift }
   66     if $ce->{pg}->{options}->{catchWarnings};
   67 
   68   # create a Translator
   69   #warn "PG: creating a Translator\n";
   70   my $translator = WeBWorK::PG::Translator->new;
   71 
   72   # set the directory hash
   73   #warn "PG: setting the directory hash\n";
   74   $translator->rh_directories({
   75     courseScriptsDirectory => $ce->{pg}->{directories}->{macros},
   76     macroDirectory         => $ce->{courseDirs}->{macros},
   77     templateDirectory      => $ce->{courseDirs}->{templates},
   78     tempDirectory          => $ce->{courseDirs}->{html_temp},
   79   });
   80 
   81   # evaluate modules and "extra packages"
   82   #warn "PG: evaluating modules and \"extra packages\"\n";
   83   my @modules = @{ $ce->{pg}->{modules} };
   84   foreach my $module_packages_ref (@modules) {
   85     my ($module, @extra_packages) = @$module_packages_ref;
   86     # the first item is the main package
   87     $translator->evaluate_modules($module);
   88     # the remaining items are "extra" packages
   89     $translator->load_extra_packages(@extra_packages);
   90   }
   91 
   92   # set the environment (from defineProblemEnvir)
   93   #warn "PG: setting the environment (from defineProblemEnvir)\n";
   94   my $envir = defineProblemEnvir(
   95     $ce,
   96     $user,
   97     $key,
   98     $set,
   99     $problem,
  100     $psvn,
  101     $formFields,
  102     $translationOptions,
  103   );
  104   $translator->environment($envir);
  105 
  106   # initialize the Translator
  107   #warn "PG: initializing the Translator\n";
  108   $translator->initialize();
  109 # $translator->dumpSafe;   # debugging code
  110 ###############################################################################
  111 #   Preload the macros files which are used routinely:  PG.pl, dangerousMacros.pl, IO.pl
  112 #   PGbasicmacros.pl and PGanswermacros.pl
  113 #   Preloading the last two files safes a significant amount of time.
  114 ###############################################################################
  115 
  116   #  IO.pl, PG.pl, and dangerousMacros.pl are loaded using unrestricted_load
  117   # This is hard wired into the Translator::pre_load_macro_files subroutine
  118   # I'd like to change this at some point to have the same sort of interface to global.conf
  119   # that the module loading does -- have a list of macros to load unrestrictedly.
  120 
  121 #    This has been replaced by the pre_load_macro_files subroutine.  It loads AND caches the files.
  122 #   While PG.pl and dangerousMacros are not large, they are referred to by PGbasicmacros and PGanswermacros.
  123 #   Because these are loaded into the cached name space (e.g. Safe::Root1::) all calls to, say NEW_ANSWER_NAME
  124 #   are actually calls to Safe::Root1::NEW_ANSWER_NAME.  It is useful to have these names inside the Safe::Root1:
  125 #   cached safe compartment.  (NEW_ANSWER_NAME and all other subroutine names are also automatically exported into
  126 #   the current safe compartment Safe::Rootx::
  127 
  128 #   The headers of both PGbasicmacros and PGanswermacros has code that insures that the constants used are imported into
  129 #   the current safe compartment.  This involves evaluating references to, say $main::displayMode, at runtime to insure that main
  130 #   refers to Safe::Rootx:: and NOT to Safe::Root1::, which is the value of main:: at compile time.
  131 
  132 
  133 ###############################################################################
  134 #   TO ENABLE CACHEING UNCOMMENT THE CACHEING CODE AND COMMENT OUT THE STANDARD LOADING CODE
  135 #   On webwork3  cached code is .2 seconds faster than non-cached code for an existing child.
  136 
  137 #   CACHING CODE:
  138 #   $translator->pre_load_macro_files($WeBWorK::PG::Local::safeCache, $ce->{pg}->{directories}->{macros},
  139 #       'PG.pl', 'dangerousMacros.pl','IO.pl','PGbasicmacros.pl','PGanswermacros.pl');
  140 
  141 #   STANDARD LOADING CODE:
  142   foreach (qw(IO.pl PG.pl dangerousMacros.pl)) {
  143     my $macroPath = $ce->{pg}->{directories}->{macros} . "/$_";
  144     my $err = $translator->unrestricted_load($macroPath);
  145     warn "Error while loading $macroPath: |$err|" if $err;
  146   }
  147 ###############################################################################
  148 
  149   # set the opcode mask (using default values)
  150   #warn "PG: setting the opcode mask (using default values)\n";
  151   $translator->set_mask();
  152 
  153   # store the problem source
  154   #warn "PG: storing the problem source\n";
  155   my $sourceFile = $problem->source_file;
  156   $sourceFile = $ce->{courseDirs}->{templates}."/".$sourceFile
  157     unless ($sourceFile =~ /^\//);
  158   eval { $translator->source_string(readFile($sourceFile)) };
  159   if ($@) {
  160     # well, we couldn't get the problem source, for some reason.
  161     return bless {
  162       translator => $translator,
  163       head_text  => "",
  164       body_text  => <<EOF,
  165 WeBWorK::Utils::readFile($sourceFile) says:
  166 $@
  167 EOF
  168       answers    => {},
  169       result     => {},
  170       state      => {},
  171       errors     => "Failed to read the problem source file.",
  172       warnings   => $warnings,
  173       flags      => {error_flag => 1},
  174     }, $class;
  175   }
  176 
  177   # install a safety filter (&safetyFilter)
  178   #warn "PG: installing a safety filter\n";
  179   $translator->rf_safety_filter(\&safetyFilter);
  180 
  181   # write timing log entry -- the translator is now all set up
  182   writeTimingLogEntry($ce, "WeBWorK::PG::new",
  183     "initialized",
  184     "intermediate");
  185 
  186   # translate the PG source into text
  187   #warn "PG: translating the PG source into text\n";
  188   $translator->translate();
  189 
  190   # after we're done translating, we may have to clean up after the
  191   # translator:
  192 
  193   # for example, HTML_img mode uses a tempdir for dvipng's temp files.\
  194   # We have to remove it.
  195   if ($envir->{dvipngTempDir}) {
  196     rmtree($envir->{dvipngTempDir}, 0, 0);
  197   }
  198 
  199   # HTML_dpng, on the other hand, uses an ImageGenerator. We have to
  200   # render the queued equations.
  201   if ($envir->{imagegen}) {
  202     my $sourceFile = $ce->{courseDirs}->{templates} . "/" . $problem->source_file;
  203     my %mtimeOption = -e $sourceFile
  204       ? (mtime => (stat $sourceFile)[9])
  205       : ();
  206 
  207     $envir->{imagegen}->render(
  208       refresh => $translationOptions->{refreshMath2img},
  209       %mtimeOption,
  210     );
  211   }
  212 
  213   my ($result, $state); # we'll need these on the other side of the if block!
  214   if ($translationOptions->{processAnswers}) {
  215 
  216     # process student answers
  217     #warn "PG: processing student answers\n";
  218     $translator->process_answers($formFields);
  219 
  220     # retrieve the problem state and give it to the translator
  221     #warn "PG: retrieving the problem state and giving it to the translator\n";
  222     $translator->rh_problem_state({
  223       recorded_score =>       $problem->status,
  224       num_of_correct_ans =>   $problem->num_correct,
  225       num_of_incorrect_ans => $problem->num_incorrect,
  226     });
  227 
  228     # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by
  229     # the PG macro package (PG.pl)
  230     #warn "PG: determining an entry order\n";
  231     my @answerOrder =
  232       $translator->rh_flags->{ANSWER_ENTRY_ORDER}
  233         ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} }
  234         : keys %{ $translator->rh_evaluated_answers };
  235 
  236     # install a grader -- use the one specified in the problem,
  237     # or fall back on the default from the course environment.
  238     # (two magic strings are accepted, to avoid having to
  239     # reference code when it would be difficult.)
  240     #warn "PG: installing a grader\n";
  241     my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE}
  242       || $ce->{pg}->{options}->{grader};
  243     $grader = $translator->rf_std_problem_grader
  244       if $grader eq "std_problem_grader";
  245     $grader = $translator->rf_avg_problem_grader
  246       if $grader eq "avg_problem_grader";
  247     die "Problem grader $grader is not a CODE reference."
  248       unless ref $grader eq "CODE";
  249     $translator->rf_problem_grader($grader);
  250 
  251     # grade the problem
  252     #warn "PG: grading the problem\n";
  253     ($result, $state) = $translator->grade_problem(
  254       answers_submitted  => $translationOptions->{processAnswers},
  255       ANSWER_ENTRY_ORDER => \@answerOrder,
  256     );
  257 
  258   }
  259 
  260   # write timing log entry
  261   writeTimingLogEntry($ce, "WeBWorK::PG::new", "", "end");
  262 
  263   # return an object which contains the translator and the results of
  264   # the translation process. this is DIFFERENT from the "format expected
  265   # by Webwork.pm (and I believe processProblem8, but check.)"
  266   return bless {
  267     translator => $translator,
  268     head_text  => ${ $translator->r_header },
  269     body_text  => ${ $translator->r_text   },
  270     answers    => $translator->rh_evaluated_answers,
  271     result     => $result,
  272     state      => $state,
  273     errors     => $translator->errors,
  274     warnings   => $warnings,
  275     flags      => $translator->rh_flags,
  276   }, $class;
  277 }
  278 
  279 # -----
  280 
  281 sub defineProblemEnvir {
  282   my (
  283     $ce,
  284     $user,
  285     $key,
  286     $set,
  287     $problem,
  288     $psvn,
  289     $formFields,
  290     $options,
  291   ) = @_;
  292 
  293   my %envir;
  294 
  295   # ----------------------------------------------------------------------
  296 
  297   # PG environment variables
  298   # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002
  299   # any changes are noted by "ADDED:" or "REMOVED:"
  300 
  301   # Vital state information
  302   # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img,
  303   #        texDisposition
  304 
  305   $envir{psvn}              = $set->psvn;
  306   $envir{psvnNumber}        = $envir{psvn};
  307   $envir{probNum}           = $problem->problem_id;
  308   $envir{questionNumber}    = $envir{probNum};
  309   $envir{fileName}          = $problem->source_file;
  310   $envir{probFileName}      = $envir{fileName};
  311   $envir{problemSeed}       = $problem->problem_seed;
  312   $envir{displayMode}       = translateDisplayModeNames($options->{displayMode});
  313   $envir{languageMode}      = $envir{displayMode};
  314   $envir{outputMode}        = $envir{displayMode};
  315   $envir{displayHintsQ}     = $options->{showHints};
  316   $envir{displaySolutionsQ} = $options->{showSolutions};
  317   # FIXME: this is HTML_img specific
  318   #$envir{refreshMath2img}   = $options->{refreshMath2img};
  319   $envir{texDisposition}    = "pdf"; # in webwork-modperl, we use pdflatex
  320 
  321   # Problem Information
  322   # ADDED: courseName, formatedDueDate
  323 
  324   $envir{openDate}            = $set->open_date;
  325   $envir{formattedOpenDate}   = formatDateTime($envir{openDate});
  326   $envir{dueDate}             = $set->due_date;
  327   $envir{formattedDueDate}    = formatDateTime($envir{dueDate});
  328   $envir{formatedDueDate}     = $envir{formattedDueDate}; # typo in many header files
  329   $envir{answerDate}          = $set->answer_date;
  330   $envir{formattedAnswerDate} = formatDateTime($envir{answerDate});
  331   $envir{numOfAttempts}       = ($problem->num_correct || 0) + ($problem->num_incorrect || 0);
  332   $envir{problemValue}        = $problem->value;
  333   $envir{sessionKey}          = $key;
  334   $envir{courseName}          = $ce->{courseName};
  335 
  336   # Student Information
  337   # ADDED: studentID
  338 
  339   $envir{sectionName}      = $user->section;
  340   $envir{sectionNumber}    = $envir{sectionName};
  341   $envir{recitationName}   = $user->recitation;
  342   $envir{recitationNumber} = $envir{recitationName};
  343   $envir{setNumber}        = $set->set_id;
  344   $envir{studentLogin}     = $user->user_id;
  345   $envir{studentName}      = $user->first_name . " " . $user->last_name;
  346   $envir{studentID}        = $user->student_id;
  347 
  348   # Answer Information
  349   # REMOVED: refSubmittedAnswers
  350 
  351   $envir{inputs_ref} = $formFields;
  352 
  353   # External Programs
  354   # ADDED: externalLaTeXPath, externalDvipngPath,
  355   #        externalGif2EpsPath, externalPng2EpsPath
  356 
  357   $envir{externalTTHPath}      = $ce->{externalPrograms}->{tth};
  358   $envir{externalLaTeXPath}    = $ce->{externalPrograms}->{latex};
  359   $envir{externalDvipngPath}   = $ce->{externalPrograms}->{dvipng};
  360   $envir{externalGif2EpsPath}  = $ce->{externalPrograms}->{gif2eps};
  361   $envir{externalPng2EpsPath}  = $ce->{externalPrograms}->{png2eps};
  362   $envir{externalGif2PngPath}  = $ce->{externalPrograms}->{gif2png};
  363 
  364   # Directories and URLs
  365   # REMOVED: courseName
  366   # ADDED: dvipngTempDir
  367 
  368   $envir{cgiDirectory}           = undef;
  369   $envir{cgiURL}                 = undef;
  370   $envir{classDirectory}         = undef;
  371   $envir{courseScriptsDirectory} = $ce->{pg}->{directories}->{macros}."/";
  372   $envir{htmlDirectory}          = $ce->{courseDirs}->{html}."/";
  373   $envir{htmlURL}                = $ce->{courseURLs}->{html}."/";
  374   $envir{macroDirectory}         = $ce->{courseDirs}->{macros}."/";
  375   $envir{templateDirectory}      = $ce->{courseDirs}->{templates}."/";
  376   $envir{tempDirectory}          = $ce->{courseDirs}->{html_temp}."/";
  377   $envir{tempURL}                = $ce->{courseURLs}->{html_temp}."/";
  378   $envir{scriptDirectory}        = undef;
  379   $envir{webworkDocsURL}         = $ce->{webworkURLs}->{docs}."/";
  380   # FIXME: this is HTML_img mode-specific
  381   #$envir{dvipngTempDir}          = $options->{displayMode} eq 'images'
  382   # ? makeTempDirectory($envir{tempDirectory}, "webwork-dvipng")
  383   # : undef;
  384 
  385   # Information for sending mail
  386 
  387   $envir{mailSmtpServer} = $ce->{mail}->{smtpServer};
  388   $envir{mailSmtpSender} = $ce->{mail}->{smtpSender};
  389   $envir{ALLOW_MAIL_TO}  = $ce->{mail}->{allowedRecipients};
  390 
  391   # Default values for evaluating answers
  392 
  393   my $ansEvalDefaults = $ce->{pg}->{ansEvalDefaults};
  394   $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults);
  395 
  396   # ----------------------------------------------------------------------
  397 
  398   my $basename = "equation-$envir{psvn}.$envir{probNum}";
  399   $basename .= ".$envir{problemSeed}" if $envir{problemSeed};
  400 
  401   # Object for generating equation images
  402   $envir{imagegen} = WeBWorK::PG::ImageGenerator->new(
  403     tempDir  => $ce->{webworkDirs}->{tmp}, # global temp dir
  404     dir  => $envir{tempDirectory},
  405     url  => $envir{tempURL},
  406     basename => $basename,
  407     latex  => $envir{externalLaTeXPath},
  408     dvipng   => $envir{externalDvipngPath},
  409   );
  410 
  411   # Other things...
  412   $envir{QUIZ_PREFIX}              = $options->{QUIZ_PREFIX}; # used by quizzes
  413   $envir{PROBLEM_GRADER_TO_USE}    = $ce->{pg}->{options}->{grader};
  414   $envir{PRINT_FILE_NAMES_FOR}     = $ce->{pg}->{specialPGEnvironmentVars}->{PRINT_FILE_NAMES_FOR};
  415 
  416   # variables for interpreting capa problems.
  417   $envir{CAPA_Tools}               = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_Tools};
  418   $envir{CAPA_MCTools}             = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_MCTools};
  419   $envir{CAPA_Graphics_URL}        = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_Graphics_URL};
  420   $envir{CAPA_GraphicsDirectory}   = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_GraphicsDirectory};
  421 
  422   return \%envir;
  423 }
  424 
  425 sub translateDisplayModeNames($) {
  426   my $name = shift;
  427   return {
  428     tex           => "TeX",
  429     plainText     => "HTML",
  430     formattedText => "HTML_tth",
  431     images        => "HTML_dpng", # "HTML_img",
  432   }->{$name};
  433 }
  434 
  435 sub safetyFilter {
  436   my $answer = shift; # accepts one answer and checks it
  437   my $submittedAnswer = $answer;
  438   $answer = '' unless defined $answer;
  439   my ($errorno);
  440   $answer =~ tr/\000-\037/ /;
  441   # Return if answer field is empty
  442   unless ($answer =~ /\S/) {
  443     #$errorno = "<BR>No answer was submitted.";
  444     $errorno = 0;  ## don't report blank answer as error
  445     return ($answer,$errorno);
  446   }
  447   # replace ^ with **    (for exponentiation)
  448   # $answer =~ s/\^/**/g;
  449   # Return if forbidden characters are found
  450   unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\[\]\(\)\,\|]+$/ )  {
  451     $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
  452     $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>";
  453     return ($answer,$errorno);
  454   }
  455   $errorno = 0;
  456   return($answer, $errorno);
  457 }
  458 
  459 1;
  460 
  461 __END__
  462 
  463 =head1 OPERATION
  464 
  465 WeBWorK::PG goes through the following operations when constructed:
  466 
  467 =over
  468 
  469 =item Get database information
  470 
  471 Retrieve information about the current user, set, and problem from the
  472 database.
  473 
  474 =item Create a translator
  475 
  476 Instantiate a WeBWorK::PG::Translator object.
  477 
  478 =item Set the directory hash
  479 
  480 Set the translator's directory hash (courseScripts, macros, templates, and temp
  481 directories) from the course environment.
  482 
  483 =item Evaluate PG modules
  484 
  485 Using the module list from the course environment (pg->modules), perform a
  486 "use"-like operation to evaluate modules at runtime.
  487 
  488 =item Set the problem environment
  489 
  490 Use data from the user, set, and problem, as well as the course environemnt and
  491 translation options, to set the problem environment.
  492 
  493 =item Initialize the translator
  494 
  495 Call &WeBWorK::PG::Translator::initialize. What more do you want?
  496 
  497 =item Load PG.pl and dangerousMacros.pl
  498 
  499 These macros must be loaded without opcode masking, so they are loaded here.
  500 
  501 =item Set the opcode mask
  502 
  503 Set the opcode mask to the default specified by WeBWorK::PG::Translator.
  504 
  505 =item Load the problem source
  506 
  507 Give the problem source to the translator.
  508 
  509 =item Install a safety filter
  510 
  511 The safety filter is used to preprocess student input before evaluation. The
  512 default safety filter, &WeBWorK::PG::safetyFilter, is used.
  513 
  514 =item Translate the problem source
  515 
  516 Call &WeBWorK::PG::Translator::translate to render the problem source into the
  517 format given by the display mode.
  518 
  519 =item Process student answers
  520 
  521 Use form field inputs to evaluate student answers.
  522 
  523 =item Load the problem state
  524 
  525 Use values from the database to initialize the problem state, so that the
  526 grader will have a point of reference.
  527 
  528 =item Determine an entry order
  529 
  530 Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the
  531 problem. This is important for problems with dependancies among parts.
  532 
  533 =item Install a grader
  534 
  535 Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment,
  536 to install a grader.
  537 
  538 =item Grade the problem
  539 
  540 Use the selected grader to grade the problem.
  541 
  542 =back
  543 
  544 =head1 AUTHOR
  545 
  546 Written by Sam Hathaway, sh002i (at) math.rochester.edu.
  547 
  548 =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9