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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 985 - (view) (download) (as text)

1 : gage 493 ################################################################################
2 : sh002i 494 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
3 : gage 493 # $Id$
4 :     ################################################################################
5 : sh002i 404
6 : sh002i 494 package WeBWorK::PG::Translator;
7 :    
8 : sh002i 404 use strict;
9 :     use warnings;
10 :     use Opcode;
11 :     use Safe;
12 :     use Net::SMTP;
13 : sh002i 412 use WeBWorK::Utils qw(runtime_use);
14 :     use WeBWorK::PG::IO;
15 : sh002i 404
16 : sh002i 412 # 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 : gage 493 #use Exporter;
20 :     #use DynaLoader;
21 : sh002i 404
22 : gage 493
23 : sh002i 412 =head1 NAME
24 :    
25 :     WeBWorK::PG::Translator - Evaluate PG code and evaluate answers safely
26 :    
27 :     =head1 SYNPOSIS
28 :    
29 : gage 499 my $pt = new WeBWorK::PG::Translator; # create a translator;
30 : sh002i 412 $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 : sh002i 404 BEGIN {
74 : sh002i 424 # allows the use of strict within macro packages.
75 :     sub be_strict {
76 :     require 'strict.pm';
77 :     strict::import();
78 : sh002i 404 }
79 : sh002i 424
80 :     # also define in Main::, for PG modules.
81 :     sub Main::be_strict { &be_strict }
82 : sh002i 404 }
83 :    
84 : sh002i 412 =head2 evaluate_modules
85 : sh002i 404
86 : sh002i 412 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 : sh002i 424 #warn "attempting to load $_\n";
99 : sh002i 412 # 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 : sh002i 424 eval { runtime_use $_ };
104 :     warn "Failed to evaluate module $_: $@" if $@;
105 : sh002i 412 # 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 : sh002i 424 foreach (@package_list) {
127 : sh002i 412 # ensure that the name is in fact a base name
128 :     s/\.pm$// and warn "fixing your broken package name: $_.pm => $_";
129 : sh002i 424 # import symbols from the extra package
130 : sh002i 412 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 : sh002i 404 sub new {
144 :     my $class = shift;
145 :     my $safe_cmpt = new Safe; #('PG_priv');
146 :     my $self = {
147 : sh002i 412 envir => undef,
148 : sh002i 404 PG_PROBLEM_TEXT_ARRAY_REF => [],
149 : sh002i 412 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 : sh002i 404 num_of_incorrect_ans => 0, # the number of incorrect attempts
165 :     },
166 : sh002i 412 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 : sh002i 404 };
172 :     bless $self, $class;
173 :     }
174 :    
175 : sh002i 412 =pod
176 : sh002i 404
177 : sh002i 412 (b) The following routines defined within the PG module are shared:
178 : sh002i 404
179 : sh002i 412 &be_strict
180 :     &read_whole_problem_file
181 :     &convertPath
182 :     &surePathToTmpFile
183 :     &fileFromPath
184 :     &directoryFromPath
185 :     &createFile
186 : sh002i 404
187 : sh002i 412 &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 : sh002i 620 #
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 : sh002i 404 my %shared_subroutine_hash = (
241 : sh002i 620 '&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 : sh002i 662 '&createDirectory' => 'IO',
255 : sh002i 620 '&getImageDimmensions' => 'IO',
256 : sh002i 623 '&dvipng' => 'IO',
257 : sh002i 404 );
258 :    
259 :     sub initialize {
260 : gage 499 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 : sh002i 404 $safe_cmpt -> share('%envir');
268 : sh002i 412 #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 : sh002i 404 use strict;
273 : sh002i 412
274 :     # ra_included_modules is now populated independantly of @class_modules:
275 :     #$self->{ra_included_modules} = [@class_modules];
276 :    
277 : sh002i 424 $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 : sh002i 404 }
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 : sh002i 412 =head2 Safe compartment pass through macros
295 :    
296 :    
297 :    
298 :     =cut
299 :    
300 : sh002i 404 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 : sh002i 424 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 : sh002i 404 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 : sh002i 424 my $rdoResult = $safe_cmpt->rdo($filePath);
387 : sh002i 404 #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 : sh002i 424 #print STDERR "$filePath is properly loaded\n\n" if $macro_file_loaded;
401 :     $local_errors;
402 : sh002i 404 }
403 :    
404 :     sub nameSpace {
405 :     my $self = shift;
406 :     $self->{safe}->root;
407 :     }
408 :    
409 :     sub a_text {
410 :     my $self = shift;
411 : sh002i 424 @{$self->{PG_PROBLEM_TEXT_ARRAY_REF}};
412 : sh002i 404 }
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 : sh002i 412 # 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 : sh002i 404 ##############################################################################
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 : sh002i 412 =head2 Translate
584 :    
585 :    
586 :     =cut
587 :    
588 : sh002i 404 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 : sh002i 412
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 : sh002i 404 ############################################################################
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 : sh002i 412 =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 : sh002i 404 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 : sh002i 412 =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 : sh002i 404 ##########################################
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 : sh002i 412 =pod
761 : sh002i 404
762 : sh002i 412 (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 : sh002i 404 ## 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 : sh002i 412 =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 : sh002i 404 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 : gage 499 =head3 process_answers
844 : sh002i 412
845 :    
846 :     $obj->process_answers()
847 :    
848 :    
849 :     =cut
850 :    
851 :    
852 : sh002i 404 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 : sh002i 424 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 : sh002i 404
882 : sh002i 424 # 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)');
891 : gage 499 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 : sh002i 424 } else {
898 :     warn "Error in PGtranslator5.pm::process_answers: Answer $ans_name:<BR>\n Unrecognized evaluator type |", ref($rf_fun), "|";
899 :     }
900 : sh002i 404
901 : sh002i 424 $SIG{__DIE__} = $save_SIG_die_trap;
902 : sh002i 404
903 :    
904 : sh002i 424 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 : sh002i 404 }
916 :    
917 : sh002i 412
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 : sh002i 404 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 : sh002i 424 my $rh_evaluated_answers = shift;
1052 : sh002i 404 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 : sh002i 424 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 : sh002i 404 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 : sh002i 424 $problem_state{num_of_correct_ans}++ if $total == $count;
1093 : sh002i 404 $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 : sh002i 412 ## 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 : sh002i 404 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 : sh002i 412 =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 : sh002i 404 no strict; # this is important -- I guess because eval operates on code which is not written with strict in mind.
1233 :    
1234 : sh002i 412
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 : sh002i 404 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 : sh002i 412 =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 : sh002i 404 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 : sh002i 424 $SIG{'FPE'} = $save_SIG_FPE_trap if defined $save_SIG_FPE_trap;
1316 : sh002i 404 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