Parent Directory
|
Revision Log
Changes that allow these files to work with optimized version of WeBWorK2.0 which uses caching of macro files. --Mike
1 #!/usr/local/bin/webwork-perl 2 3 # This file provided the fundamental macros for the pg language 4 # These macros define the interface between the problems written by 5 # the professor and the processing which occurs in the script 6 # processProblem.pl 7 8 9 BEGIN { 10 be_strict(); 11 } 12 13 sub _PG_init{ 14 15 } 16 17 #package PG; 18 19 20 =head1 NAME 21 22 PG.pl --- located in the courseScripts directory. 23 Defines the Program Generating language at the most basic level. 24 25 =head1 SYNPOSIS 26 27 The basic PG problem structure: 28 29 DOCUMENT(); # should be the first statment in the problem 30 loadMacros(.....); # (optional) load other macro files if needed. 31 # (loadMacros is defined in F<dangerousMacros.pl>) 32 33 HEADER_TEXT(...); # (optional) used only for inserting javaScript into problems. 34 35 # # insert text of problems 36 TEXT("Problem text to be", 37 "displayed. Enter 1 in this blank:", 38 ANS_RULE(1,30) # ANS_RULE() defines an answer blank 30 characters long. 39 # It is defined in F<PGbasicmacros.pl> 40 ); 41 42 43 ANS( answer_evalutors); # see F<PGanswermacros.pl> for examples of answer evaluatiors. 44 45 ENDDOCUMENT() # must be the last statement in the problem 46 47 48 49 =head1 DESCRIPTION 50 51 As described in the synopsis, this file and the macros C<DOCUMENT()> and C<ENDDOCUMENT()> determine 52 the interface between problems written in the PG language and the rest of B<WeBWorK>, in particular 53 the subroutine C<createPGtext(()> in the file F<translate.pl>. 54 55 C<DOCUMENT()> must be the first statement in each problem template. 56 It initializes variables, 57 in particular all of the contents of the 58 environment variable become defined in the problem enviroment. 59 (See 60 L</webwork_system_html/docs/techdescription/pglanguage/PGenvironment.html>) 61 62 ENDDOCUMENT() must the last executable statement in any problem template. It returns 63 the rendered problem, answer evaluators and other flags to the rest of B<WeBWorK>, specificially 64 to the routine C<createPGtext()> defined in F<translate.pl> 65 66 67 The C<HEADER_TEXT()>, C<TEXT()>, and C<ANS()> functions load the 68 header text string, the problem text string. 69 and the answer evaulator queue respectively. 70 71 72 =cut 73 74 75 # Private variables for the PG.pl file. 76 77 my ($STRINGforOUTPUT, $STRINGforHEADER_TEXT, @PG_ANSWERS, @PG_UNLABELED_ANSWERS); 78 my %PG_ANSWERS_HASH ; 79 # my variables are unreliable if two DOCUMENTS were to be called before and ENDDOCUMENT 80 # there could be conflicts. As I understand the behavior of the Apache child 81 # this cannot occur -- a child finishes with one request before obtaining the next 82 83 # DOCUMENT must come early in every .pg file, before any answers or text are 84 # defined. It initializes the variables. 85 # It can appear only once. 86 87 =head2 DOCUMENT() 88 89 C<DOCUMENT()> must be the first statement in each problem template. It can 90 only be used once in each problem. 91 92 C<DOCUMENT()> initializes some empty variables and via C<INITIALIZE_PG()> unpacks the 93 variables in the C<%envir> variable which is implicitly passed to the problem. It must 94 be the first statement in any problem template. It 95 also unpacks any answers submitted and places them in the C<@submittedAnswer> list, 96 saves the problem seed in C<$PG_original_problemSeed> in case you need it later, and 97 initializes the pseudo random number generator object in C<$PG_random_generator>. 98 99 You can reset the standard number generator using the command: 100 101 $PG_random_generator->srand($new_seed_value); 102 103 (See also C<SRAND> in the L<PGbasicmacros.pl> file.) 104 105 The 106 environment variable contents is defined in 107 L</webwork_system_html/docs/techdescription/pglanguage/PGenvironment.html> 108 109 110 =cut 111 112 sub DOCUMENT { 113 114 $STRINGforOUTPUT =""; 115 $STRINGforHEADER_TEXT =""; 116 @PG_ANSWERS=(); 117 118 @PG_UNLABELED_ANSWERS = (); 119 %PG_ANSWERS_HASH = (); 120 # FIXME: We are initializing these variables into both Safe::Root1 (the cached safe compartment) 121 # and Safe::Root2 (the current one) 122 # There is a good chance they won't be properly updated in one or the other of these compartments. 123 124 # @main::PG_ANSWER_ENTRY_ORDER = (); 125 # $main::ANSWER_PREFIX = 'AnSwEr'; 126 # %main::PG_FLAGS=(); #global flags 127 # $main::showPartialCorrectAnswers = 0 unless defined($main::showPartialCorrectAnswers ); 128 # $main::showHint = 1 unless defined($main::showHint); 129 # $main::solutionExists =0; 130 # $main::hintExists =0; 131 # %main::gifs_created = (); 132 eval(q! 133 @main::PG_ANSWER_ENTRY_ORDER = (); 134 $main::ANSWER_PREFIX = 'AnSwEr'; 135 %main::PG_FLAGS=(); #global flags 136 $main::showPartialCorrectAnswers = 0 unless defined($main::showPartialCorrectAnswers ); 137 $main::showHint = 1 unless defined($main::showHint); 138 $main::solutionExists =0; 139 $main::hintExists =0; 140 %main::gifs_created = (); 141 !); 142 # warn eval(q! "PG.pl: The envir variable $main::{envir} is".join(" ",%main::envir)!); 143 my $rh_envir = eval(q!\%main::envir!); 144 my %envir = %$rh_envir; 145 no strict; 146 foreach my $var (keys %envir) { 147 eval(q!$main::!.$var.q! = $main::envir{!.$var.q!}! ); #whew!! makes sure $var is interpolated but $main:: is evaluated at run time. 148 # warn eval(q! "var $var is defined ". $main::!.$var); 149 warn "Problem defining ", q{\$main::}.$var, " while initializing the PG problem: $@" if $@; 150 } 151 use strict; 152 153 154 155 eval(q! 156 @main::submittedAnswers = @{$main::refSubmittedAnswers} if defined($main::refSubmittedAnswers); 157 $main::PG_original_problemSeed = $main::problemSeed; 158 $main::PG_random_generator = new PGrandom($main::problemSeed) || die "Can't create random number generator."; 159 $main::ans_rule_count = 0; # counts questions 160 161 # end unpacking of environment variables. 162 $main::QUIZ_PREFIX = '' unless defined($main::QUIZ_PREFIX) 163 164 !); 165 # @main::submittedAnswers = @{$main::refSubmittedAnswers} if defined($main::refSubmittedAnswers); 166 # $main::PG_original_problemSeed = $main::problemSeed; 167 # $main::PG_random_generator = new PGrandom($main::problemSeed) || die "Can't create random number generator."; 168 # $main::ans_rule_count = 0; # counts questions 169 170 # end unpacking of environment variables. 171 # $main::QUIZ_PREFIX = '' unless defined($main::QUIZ_PREFIX) 172 173 } 174 175 sub inc_ans_rule_count { 176 eval(q!++$main::ans_rule_count!); # evalute at runtime to get correct main:: 177 } 178 # HEADER_TEXT is for material which is destined to be placed in the header of the html problem -- such 179 # as javaScript code. 180 181 =head2 HEADER_TEXT() 182 183 184 HEADER_TEXT("string1", "string2", "string3"); 185 186 The C<HEADER_TEXT()> 187 function concatenates its arguments and places them in the output 188 header text string. It is used for material which is destined to be placed in 189 the header of the html problem -- such as javaScript code. 190 It can be used more than once in a file. 191 192 193 =cut 194 195 sub HEADER_TEXT { 196 my @in = @_; 197 $STRINGforHEADER_TEXT .= join(" ",@in); 198 } 199 200 # TEXT is the function which defines text which will appear in the problem. 201 # All text must be an argument to this function. Any other statements 202 # are calculations (done in perl) which will not directly appear in the 203 # output. Think of this as the "print" function for the .pg language. 204 # It can be used more than once in a file. 205 206 =head2 TEXT() 207 208 TEXT("string1", "string2", "string3"); 209 210 The C<TEXT()> function concatenates its arguments and places them in the output 211 text string. C<TEXT()> is the function which defines text which will appear in the problem. 212 All text must be an argument to this function. Any other statements 213 are calculations (done in perl) which will not directly appear in the 214 output. Think of this as the "print" function for the .pg language. 215 It can be used more than once in a file. 216 217 =cut 218 219 sub TEXT { 220 my @in = @_; 221 $STRINGforOUTPUT .= join(" ",@in); 222 } 223 224 225 226 =head2 ANS() 227 228 ANS(answer_evaluator1, answer_evaluator2, answer_evaluator3,...) 229 230 Places the answer evaluators in the unlabeled answer_evaluator queue. They will be paired 231 with unlabeled answer rules (answer entry blanks) in the order entered. This is the standard 232 method for entering answers. 233 234 LABELED_ANS(answer_evaluater_name1, answer_evaluator1, answer_evaluater_name2,answer_evaluator2,...) 235 236 Places the answer evaluators in the labeled answer_evaluator hash. This allows pairing of 237 labeled answer evaluators and labeled answer rules which may not have been entered in the same 238 order. 239 240 =cut 241 242 sub ANS{ # store answer evaluators which have not been explicitly labeled 243 my @in = @_; 244 while (@in ) { 245 warn("<BR><B>Error in ANS:$in[0]</B> -- inputs must be references to 246 subroutines<BR>") 247 unless ref($in[0]); 248 push(@PG_ANSWERS, shift @in ); 249 } 250 } 251 sub LABELED_ANS { #a better alias for NAMED_ANS 252 &NAMED_ANS; 253 } 254 255 sub NAMED_ANS{ # store answer evaluators which have been explicitly labeled (submitted in a hash) 256 my @in = @_; 257 while (@in ) { 258 my $label = shift @in; 259 $label = $main::QUIZ_PREFIX.$label; 260 my $ans_eval = shift @in; 261 TEXT("<BR><B>Error in NAMED_ANS:$in[0]</B> 262 -- inputs must be references to subroutines<BR>") 263 unless ref($ans_eval); 264 $PG_ANSWERS_HASH{$label}= $ans_eval; 265 } 266 } 267 sub RECORD_ANS_NAME { # this maintains the order in which the answer rules are printed. 268 my $label = shift; 269 push(@main::PG_ANSWER_ENTRY_ORDER, $label); 270 $label; 271 } 272 273 sub NEW_ANS_NAME { # this keeps track of the answers which are entered implicitly, 274 # rather than with a specific label 275 my $number=shift; 276 my $label = "$main::QUIZ_PREFIX$main::ANSWER_PREFIX$number"; 277 push(@PG_UNLABELED_ANSWERS,$label); 278 $label; 279 } 280 sub ANS_NUM_TO_NAME { # This converts a number to an answer label for use in 281 # radio button and check box answers. No new answer 282 # name is recorded. 283 my $number=shift; 284 my $label = "$main::QUIZ_PREFIX$main::ANSWER_PREFIX$number"; 285 $label; 286 } 287 288 my $vecnum; 289 290 sub NEW_ANS_ARRAY_NAME { # this keeps track of the answers which are entered implicitly, 291 # rather than with a specific label 292 my $number=shift; 293 $vecnum = 0; 294 my $row = shift; 295 my $col = shift; 296 my $label = "ArRaY"."$number"."["."$vecnum".","."$row".","."$col"."]"; 297 push(@PG_UNLABELED_ANSWERS,$label); 298 $label; 299 } 300 301 sub NEW_ANS_ARRAY_NAME_EXTENSION { # this keeps track of the answers which are entered implicitly, 302 # rather than with a specific label 303 my $number=shift; 304 my $row = shift; 305 my $col = shift; 306 if( $row == 0 && $col == 0 ){ 307 $vecnum += 1; 308 } 309 my $label = "ArRaY"."$number"."["."$vecnum".","."$row".","."$col"."]"; 310 $label; 311 } 312 313 # ENDDOCUMENT must come at the end of every .pg file. 314 # It exports the resulting text of the problem, the text to be used in HTML header material 315 # (for javaScript), the list of answer evaluators and any other flags. It can appear only once and 316 # it MUST be the last statement in the problem. 317 318 =head2 ENDDOCUMENT() 319 320 ENDDOCUMENT() must the last executable statement in any problem template. It can 321 only appear once. It returns 322 an array consisting of 323 324 A reference to a string containing the rendered text of the problem. 325 A reference to a string containing text to be placed in the header 326 (for javaScript) 327 A reference to the array containing the answer evaluators. 328 (May be changed to a hash soon.) 329 A reference to an associative array (hash) containing various flags. 330 331 The following flags are set by ENDDOCUMENT: 332 (1) showPartialCorrectAnswers -- determines whether students are told which 333 of their answers in a problem are wrong. 334 (2) recordSubmittedAnswers -- determines whether students submitted answers 335 are saved. 336 (3) refreshCachedImages -- determines whether the cached image of the problem 337 in typeset mode is always refreshed (i.e. setting this to 1 means cached 338 images are not used). 339 (4) solutionExits -- indicates the existence of a solution. 340 (5) hintExits -- indicates the existence of a hint. 341 (6) showHintLimit -- determines the number of attempts after which hint(s) will be shown 342 343 (7) PROBLEM_GRADER_TO_USE -- chooses the problem grader to be used in this order 344 (a) A problem grader specified by the problem using: 345 install_problem_grader(\&grader); 346 (b) One of the standard problem graders defined in PGanswermacros.pl when set to 347 'std_problem_grader' or 'avg_problem_grader' by the environment variable 348 $PG_environment{PROBLEM_GRADER_TO_USE} 349 (c) A subroutine referenced by $PG_environment{PROBLEM_GRADER_TO_USE} 350 (d) The default &std_problem_grader defined in PGanswermacros.pl 351 352 353 =cut 354 355 sub ENDDOCUMENT { 356 357 my $index=0; 358 foreach my $label (@PG_UNLABELED_ANSWERS) { 359 if ( defined($PG_ANSWERS[$index]) ) { 360 $PG_ANSWERS_HASH{"$label"}= $PG_ANSWERS[$index]; 361 } else { 362 warn "No answer provided by instructor for answer $label"; 363 } 364 $index++; 365 } 366 367 $STRINGforOUTPUT .="\n"; 368 eval q{ #make sure that "main" points to the current safe compartment by evaluating these lines. 369 $main::PG_FLAGS{'showPartialCorrectAnswers'} = $main::showPartialCorrectAnswers; 370 $main::PG_FLAGS{'recordSubmittedAnswers'} = $main::recordSubmittedAnswers; 371 $main::PG_FLAGS{'refreshCachedImages'} = $main::refreshCachedImages; 372 $main::PG_FLAGS{'hintExists'} = $main::hintExists; 373 $main::PG_FLAGS{'showHintLimit'} = $main::showHint; 374 $main::PG_FLAGS{'solutionExists'} = $main::solutionExists; 375 $main::PG_FLAGS{ANSWER_ENTRY_ORDER} = \@main::PG_ANSWER_ENTRY_ORDER; 376 $main::PG_FLAGS{ANSWER_PREFIX} = $main::ANSWER_PREFIX; 377 # install problem grader 378 if (defined($main::PG_FLAGS{PROBLEM_GRADER_TO_USE}) ) { 379 # problem grader defined within problem -- no further action needed 380 } elsif ( defined( $main::envir{PROBLEM_GRADER_TO_USE} ) ) { 381 if (ref($main::envir{PROBLEM_GRADER_TO_USE}) eq 'CODE' ) { # user defined grader 382 $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} = $main::envir{PROBLEM_GRADER_TO_USE}; 383 } elsif ($main::envir{PROBLEM_GRADER_TO_USE} eq 'std_problem_grader' ) { 384 if (defined(&std_problem_grader) ){ 385 $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} = \&std_problem_grader; # defined in PGanswermacros.pl 386 } # std_problem_grader is the default in any case so don't give a warning. 387 } elsif ($main::envir{PROBLEM_GRADER_TO_USE} eq 'avg_problem_grader' ) { 388 if (defined(&avg_problem_grader) ){ 389 $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} = \&avg_problem_grader; # defined in PGanswermacros.pl 390 } 391 #else { # avg_problem_grader will be installed by PGtranslator so there is no need for a warning. 392 # warn "The problem grader 'avg_problem_grader' has not been defined. Has PGanswermacros.pl been loaded?"; 393 #} 394 } else { 395 warn "Error: $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} is not a known program grader."; 396 } 397 } elsif (defined(&std_problem_grader)) { 398 $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} = \&std_problem_grader; # defined in PGanswermacros.pl 399 } else { 400 # PGtranslator will install its default problem grader 401 } 402 403 warn "ERROR: The problem grader is not a subroutine" unless ref( $main::PG_FLAGS{PROBLEM_GRADER_TO_USE}) eq 'CODE' 404 or $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} = 'std_problem_grader' 405 or $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} = 'avg_problem_grader'; 406 # return results 407 }; 408 (\$STRINGforOUTPUT, \$STRINGforHEADER_TEXT,\%PG_ANSWERS_HASH,eval(q!\%main::PG_FLAGS!)); 409 } 410 411 412 413 =head2 INITIALIZE_PG() 414 415 This is executed each C<DOCUMENT()> is called. For backward compatibility 416 C<loadMacros> also checks whether the C<macroDirectory> has been defined 417 and if not, it runs C<INITIALIZE_PG()> and issues a warning. 418 419 =cut 420 421 422 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |