WeBWorK::PG::Translator - Evaluate PG code and evaluate answers safely
my $pt = WeBWorK::PG::Translator->new; # create a translator
$pt->environment(\%envir); # provide the environment variable for the problem
$pt->initialize; # initialize the translator
$pt->set_mask; # set the operation mask for the translator safe compartment
$pt->source_string($source); # provide the source string for the problem
# or
$pt->source_file($sourceFilePath); # provide the proble file containing the source
# Load the unprotected macro files.
# These files are evaluated with the Safe compartment wide open.
# Other macros are loaded from within the problem using loadMacros.
# This should not be done if the safe cache is used which is only the case if $ENV{MOJO_MODE} exists.
$pt->unrestricted_load("${pgMacrosDirectory}PG.pl");
$pt->translate; # translate the problem (the following pieces of information are created)
$PG_PROBLEM_TEXT_REF = $pt->r_text; # reference to output text for the body of problem
$PG_HEADER_TEXT_REF = $pt->r_header; # reference to text for the header in HTML output
$PG_POST_HEADER_TEXT_REF = $pt->r_post_header;
$PG_ANSWER_HASH_REF = $pt->rh_correct_answers; # a hash of answer evaluators
$PG_FLAGS_REF = $pt->rh_flags; # misc. status flags.
$pt->process_answers; # evaluates all of the answers
my $rh_answer_results = $pt->rh_evaluated_answers; # provides a hash of the results of evaluating the answers.
my $rh_problem_result = $pt->grade_problem(%options); # grades the problem.
$pt->post_process_content; # Execute macro or problem hooks that further modify the problem content.
$pt->stringify_answers; # Convert objects to strings in the answer hash
This module defines an object which will translate a problem written in the Problem Generating (PG) language
Adds modules to the list of modules which can be used by the PG problems.
For example,
$obj->evaluate_modules('LaTeXImage', 'DragNDrop');
adds modules to the LaTeXImage
and DragNDrop
modules.
Loads extra packages for modules that contain more than one package. Works in conjunction with evaluate_modules. It is assumed that the file containing the extra packages (along with the base package name which is the same as the name of the file minus the .pm extension) has already been loaded using evaluate_modules.
Usage: $obj->load_extra_packages('AlgParserWithImplicitExpand', 'ExprWithImplicitExpand');
Creates the translator object.
The following translator methods are shared to the safe compartment:
&PG_answer_eval
&PG_restricted_eval
&PG_macro_file_eval
Also all methods that are exported by WeBWorK::PG::IO are shared.
In addition the environment hash %envir
is shared. This variable is unpacked when PG.pl is run.
Limit allowed operations in the safe compartment. Only the certain operations can be used within PG problems and the PG macro files. These include the subroutines shared with the safe compartment as defined above and most Perl commands which do not involve file access, access to the system or evaluation.
Specifically the following are allowed:
time
- Gives the current Unix time.
atan, sin, cos, exp, log, sqrt
- Arithemetic commands. More are defined in PGauxiliaryFunctions.pl
The following are specifically not allowed:
eval, unlink, symlink, system, exec, print, require
This routine processes error messages by fixing file names and adding traceback information. It loops through the function calls via caller() in order to give more information about where the error occurred. Since the loadMacros() files and the .pg file itself are handled via various kinds of eval calls, the caller() information does not contain the file names. So we have saved them in the $main::__files__ hash, which we look up here and use to replace the (eval nnn) file names that are in the caller stack. We shorten the filenames by removing the templates or root directories when possible, so they are easier to read.
We skip any nested calls to Parser:: or Value:: so that these act more like perl built-in functions.
We stop when we find a routine in the WeBWorK:: package, or an __ANON__ routine, in order to avoid reporting the PG translator calls that surround the pg file. Finally, there is usually one more eval before that, so we remove it as well.
File names are shortened, when possible, by replacing the templates directory with [TMPL], the WeBWorK root directory by [WW] and the PG root directory by [PG].
Preprocess the problem text
The input text is subjected to some global replacements.
First every incidence of
BEGIN_TEXT
problem text
END_TEXT
is replaced by
TEXT(EV3(<<'END_TEXT'));
problem text
END_TEXT
The first construction is syntactic sugar for the second. This is explained in PGbasicmacros.pl
.
Second every incidence of \ (backslash) is replaced by \\ (double backslash).
Third each incidence of ~~ is replaced by a single backslash.
This is done to alleviate a basic incompatibility between TeX and Perl. TeX uses backslashes to denote a command word (as opposed to text which is to be entered literally). Perl uses backslashes to escape the following symbol. This escape mechanism takes place immediately when a Perl script is compiled and takes place throughout the code and within every quoted string (both double and single quoted strings) with the single exception of single quoted "here" documents. That is backlashes which appear in
TEXT(<<'EOF');
... text including \{ \} for example
EOF
are the only ones not immediately evaluated. This behavior makes it very difficult to use TeX notation for defining mathematics within text.
The initial global replacement, before compiling a PG problem, allows one to use backslashes within text without doubling them. (The anomalous behavior inside single quoted "here" documents is compensated for by the behavior of the evaluation macro EV3.) This makes typing TeX easy, but introduces one difficulty in entering normal Perl code.
The second global replacement provides a work around for this. That is to use ~~ when you would ordinarily use a backslash in Perl code. In order to define a carriage return use ~~n rather than \n; in order to define a reference to a variable you must use ~~@array rather than \@array. This is annoying and a source of simple compiler errors, but must be lived with.
The problems are not evaluated in strict mode, so global variables can be used without warnings.
Note that there are several other replacements that are now done that are not documented here. See the default_preprocess_code
method for all replacements that are done.
Evaluate the problem text
Evaluate the text within the safe compartment. Save the errors. The safe compartment is a new one unless the $safeCompartment was set to zero in which case the previously defined safe compartment is used. (See item 1.)
Process errors
The error provided by Perl is truncated slightly and returned. In the text string which would normally contain the rendered problem.
The original text string is given line numbers and concatenated to the errors.
Prepare return values
Sets the following hash keys of the translator object: PG_PROBLEM_TEXT_REF: Reference to a string containing the rendered text. PG_HEADER_TEXT_REF: Reference to a string containing material to be placed in the header. PG_POST_HEADER_TEXT_REF: Reference to a string containing material to be placed in body above form. rh_correct_answers: Reference to an array containing the answer evaluators. Constructed from keys of $PGcore->{PG_ANSWERS_HASH}. PG_FLAGS_REF: Reference to a hash containing flags and other references: 'error_flag' is set to 1 if there were errors in rendering. rh_pgcore: The PGcore object.
$obj->rh_student_answers
$obj->process_answers()
$obj->rh_problem_state(%problem_state); # sets the current problem state
$obj->grade_problem(%form_options);
Call hooks added via macros or the problem via add_content_post_processor
to post process content. Hooks are called in the order they were added.
This method should be called in the rendering process after answer processing has occurred.
If the display mode is TeX, then each hook subroutine is passed a reference to the problem text string generated in the translate
method.
For all other display modes each hook subroutine is passed two Mojo::DOM objects. The first containing the parsed problem text string, and the second contains the parsed header text string, both of which were generated in the translate
method. After all hooks are called and modifications are made to the Mojo::DOM contents by the hooks, the Mojo::DOM objects are converted back to strings and the translator problem text and header references are updated with the contents of those strings.
PG_restricted_eval($string)
Evaluated in package 'main'. Result of last statement is returned. When called from within a safe compartment the safe compartment package is 'main'.
PG_answer_eval($string)
Evaluated in package defined by the current safe compartment. Result of last statement is returned. When called from within a safe compartment the safe compartment package is 'main'.
There is still some confusion about how these two evaluation subroutines work and how best to define them. It is useful to have two evaluation procedures since at some point one might like to make the answer evaluations more stringent.