Opening the book…
Use this section when something is wrong with your WeBWorK problem and you need to find and fix it. The mistakes below are organized by what you see (the symptom), what caused it, and exactly how to fix it. Every fix includes code you can copy and paste.
These are the most common mistakes made by problem authors, collected from real debugging sessions. If your problem is not working, there is a good chance the answer is on this page.
PGML parsing errors
PGML uses asterisks and other punctuation as formatting markers (see PGML (PG Markup Language) for full PGML syntax). When these markers appear near variables, the parser can misinterpret them and produce confusing errors.
Asterisks around variables
Symptom: '*' was not closed before paragraph ends
Cause: PGML interprets * as the start of italic text. When you wrap a variable in single asterisks, PGML tries to treat everything between them as italic content. If the variable expands to something unexpected, the italic block never closes.
Wrong:
BEGIN_PGML The answer is *[$variable]*. END_PGML
Right (option 1 -- use bold instead of italic):
BEGIN_PGML The answer is **[$variable]**. END_PGML
Right (option 2 -- remove the emphasis):
BEGIN_PGML The answer is [$variable]. END_PGML
Right (option 3 -- build emphasis in a variable):
$answer_emph = "<em>$variable</em>"; BEGIN_PGML The answer is [$answer_emph]*. END_PGML
Option 3 moves the emphasis into an HTML variable and uses [$var]* to pass the HTML through without escaping.
Bold markers around variables
Symptom: '**' was not closed before paragraph ends
Cause: Bold markers (**) break when the variable content contains characters that PGML interprets as additional formatting.
Wrong:
BEGIN_PGML At pH [$selected_ph], histidine exists in **[$correct_label]**. END_PGML
Right (remove the bold):
BEGIN_PGML At pH [$selected_ph], histidine exists in [$correct_label]. END_PGML
Right (build emphasis in a variable):
$label_bold = "<strong>$correct_label</strong>"; BEGIN_PGML At pH [$selected_ph], histidine exists in [$label_bold]*. END_PGML
Using * for bullets when you also have italic content
Symptom: Confusing PGML parse behavior. Lines that begin with * and also contain *italic* markers cause the parser to misread which asterisks are bullets and which are emphasis.
Wrong:
BEGIN_PGML * *State 1* (pH < 1.82): Description * *State 2* (pH > 1.82): Description END_PGML
Right (use dashes for bullets):
BEGIN_PGML - **State 1** (pH < 1.82): Description - **State 2** (pH > 1.82): Description END_PGML
Variable scoping: the number one surprise
This is the single most confusing issue for new PG authors. It looks like you did everything right, but the widget does not appear.
The my keyword makes variables invisible to PGML
Symptom: RadioButtons renders as a plain text input box instead of radio buttons. Grading fails with Answer evaluator was not executed due to errors.
Cause: Writing my $radio = RadioButtons(...) creates a lexical variable that PGML cannot see. Think of it this way: PGML looks up variables in a shared directory (the package symbol table). The my keyword puts the variable in a private closet that PGML cannot open. When PGML encounters [_]{$radio}, it checks the shared directory, finds nothing, and falls back to a generic text input.
Wrong:
my $radio = RadioButtons(['Alpha', 'Beta'], 'Alpha');
BEGIN_PGML
[_]{$radio}
END_PGML
Right:
$radio = RadioButtons(['Alpha', 'Beta'], 'Alpha');
BEGIN_PGML
[_]{$radio}
END_PGML
The rule: Any variable referenced inside BEGIN_PGML (via [$var], [_]{$var}, or [$var]*) must NOT use my. Using my for loop counters and temporary helpers is fine, as long as those variables never appear inside a PGML block.
# OK -- my for intermediates, bare for PGML-visible results my $idx = random(0, 3, 1); $rb = RadioButtons([@choices], $choices[$idx]);
| Keyword | PGML visible? | Use for |
|---|---|---|
$var = ... (bare) |
Yes | Variables displayed or graded in PGML |
local $var = ... |
Yes | Temporary override of a package variable inside a block |
my $var = ... |
No | Helper variables, loop counters, temporary computation |
Why local works but my does not
Authors who know that my is "more modern" than local sometimes avoid local entirely. In PG, the two keywords have critically different PGML visibility. local temporarily modifies a package variable, so it stays in the symbol table and PGML can still find it. my creates a lexical variable on the Perl pad stack, which is invisible to PGML's symbol-table lookup. For PG problems, the simplest approach is to use bare package variables (no my, no local) for anything PGML needs to see.
HTML escaping: the [$var]* rule
Symptom: HTML tags show as literal text on screen. Instead of colored or bold text, the student sees raw markup like <span style="color:red;">Important</span>.
Cause: PGML escapes HTML by default for safety. Every < becomes < so the browser displays tags as text instead of rendering them.
Wrong:
$html_content = '<span style="color:red;">Important</span>'; BEGIN_PGML This is [$html_content] text. END_PGML
Right:
$html_content = '<span style="color:red;">Important</span>'; BEGIN_PGML This is [$html_content]* text. END_PGML
The rule: If a variable contains any HTML tags (<span>, <div>, <strong>, <sub>, etc.), always add * after the closing bracket. The asterisk tells PGML to pass the content through without escaping.
Missing or wrong macros
PG widgets like RadioButtons and DropDown live in separate macro files (see Common PG Macros for macro basics). If you forget the macro, the widget function is undefined and the renderer crashes.
Symptom table: missing macro errors
| Widget or feature | Required macro | What happens without it |
|---|---|---|
| DropDown / PopUp | parserPopUp.pl |
Undefined subroutine error |
| RadioButtons | parserRadioButtons.pl |
Undefined subroutine error |
| DataTable / LayoutTable | niceTables.pl |
Undefined subroutine error |
| NchooseK | PGchoicemacros.pl |
Undefined subroutine error |
How to fix
Check your loadMacros() block near the top of the file and add the missing macro. A correct block that covers common widgets:
loadMacros( 'PGstandard.pl', 'PGML.pl', 'parserRadioButtons.pl', 'parserPopUp.pl', 'niceTables.pl', 'PGcourse.pl', );
use statements are trapped
Symptom: 'require' trapped by operation mask
Cause: Perl's use statement calls require internally, and the PG sandbox blocks it for security. PG problems run on shared servers and cannot load arbitrary Perl modules.
Wrong:
use POSIX qw(floor); use List::Util qw(shuffle);
Right: Use loadMacros() for PG macros and built-in alternatives for standard functions.
# For truncation (instead of POSIX floor)
$val = int(3.7); # returns 3
# For rounding (instead of POSIX round)
$val = sprintf("%.0f", 3.7); # returns 4
Other trapped operations include eval "string", open(), system(), and backtick execution.
Blocked HTML tags
Symptom: Table content disappears or renders as broken text.
Cause: The tags <table>, <tr>, <td>, and <th> are blocked by the HTML whitelist. The renderer strips them out silently.
Wrong:
BEGIN_PGML <table> <tr><td>Cell 1</td><td>Cell 2</td></tr> </table> END_PGML
Right: Use niceTables.pl with DataTable or LayoutTable.
loadMacros('niceTables.pl');
$table = DataTable(
[['Header 1', 'Header 2'],
['Cell 1', 'Cell 2']],
);
BEGIN_PGML
[$table]*
END_PGML
Note the * after [$table] because the variable contains HTML. niceTables.pl is the only supported way to create tables in PG problems.
MODES misuse
Symptom: The number 1 appears on screen where you expected HTML content.
Cause: MODES(...) returns 1 in eval context instead of the HTML string. This happens when MODES() is called inside a [@ ... @] eval block in PGML.
When MODES is needed: Only when wrapper HTML must be emitted from inside Perl eval blocks, typically for layout structures like two-column matching.
When MODES is NOT needed: Inline HTML like <span>, <sub>, and <sup> renders directly without MODES(). Store the HTML in a variable and use [$var]* instead.
Wrong:
BEGIN_PGML [@ MODES(HTML => '<div>content</div>') @]* END_PGML
Right:
$content = '<div>content</div>'; BEGIN_PGML [$content]* END_PGML
TeX color macros do not work. Commands like \color{} and \textcolor{} do not render in this environment. Use HTML <span> elements with inline CSS for colored text instead.
Order of operations
PG runs your code from top to bottom. If you reference a variable before it is assigned, it silently becomes an empty string with no warning.
Using variables before they are defined
Symptom: Empty output, missing content, or blank areas in the rendered problem.
Wrong:
@all_cards = ($card1, $card2, $card3); # ... 50 lines later ... $card1 = '<div>content 1</div>'; $card2 = '<div>content 2</div>'; $card3 = '<div>content 3</div>';
Right:
$card1 = '<div>content 1</div>'; $card2 = '<div>content 2</div>'; $card3 = '<div>content 3</div>'; # ... then use them ... @all_cards = ($card1, $card2, $card3);
Shuffle timing
The correct pattern: (1) define all items, (2) create the array, (3) shuffle. Never shuffle before the items exist.
# 1. Define all items first
$item1 = "content 1";
$item2 = "content 2";
$item3 = "content 3";
# 2. Create array
@all_items = ($item1, $item2, $item3);
# 3. Shuffle
@shuffled = ();
@temp = @all_items;
while (@temp) {
my $i = random(0, $#temp, 1);
push @shuffled, splice(@temp, $i, 1);
}
Hash key order is not deterministic
Symptom: Random order changes between runs even with the same seed.
Cause: Perl hash key iteration order is not guaranteed. Each run can return keys in a different order, making randomization unreproducible.
Wrong:
%items = (a => 'Apple', b => 'Banana', c => 'Cherry'); @choices = values %items; # Random order!
Right:
%items = (a => 'Apple', b => 'Banana', c => 'Cherry'); @sorted_keys = sort keys %items; $selected = list_random(@sorted_keys);
Perl-in-PG gotchas
PG is Perl, but it runs inside a security sandbox (the Safe compartment). Several standard Perl operations are blocked or behave differently.
Backslash references are broken (\@ and \%)
Symptom: Not an ARRAY reference or Not a HASH reference
Cause: The PG sandbox blocks the backslash (\) operator for creating references. Writing \@array does not produce a usable array reference.
Wrong:
@choices = ('Alpha', 'Beta', 'Gamma');
$rb = RadioButtons(\@choices, 'Alpha');
Right (option 1 -- anonymous copy, preferred):
@choices = ('Alpha', 'Beta', 'Gamma');
$rb = RadioButtons([@choices], 'Alpha');
Right (option 2 -- PG double-tilde operator):
@choices = ('Alpha', 'Beta', 'Gamma');
$rb = RadioButtons(~~@choices, 'Alpha');
The same rule applies to hash references:
# Wrong: $href = \%data;
# Right: $href = {%data};
# Right: $href = ~~%data;
Loops do not work inside BEGIN_TEXT
Symptom: No output, empty section where the loop content should appear.
Cause: BEGIN_TEXT...END_TEXT is a string construction block, not Perl code execution. A foreach loop inside it becomes part of the string rather than running as code.
Wrong:
BEGIN_TEXT
foreach my $item (@items) {
\{ $item \}
}
END_TEXT
Right (build the string before BEGIN_TEXT):
$all_items_html = '';
foreach my $item (@items) {
$all_items_html .= $item;
}
BEGIN_TEXT
\{ $all_items_html \}
END_TEXT
Answer field naming
Symptom: The answer is always marked wrong, or grading silently fails (see Answer blanks and answers for answer blank syntax).
Cause: The ANS() function expects an answer field created by ans_rule(). When you create a hidden input with JavaScript or raw HTML, its name attribute does not match the name that ANS() is looking for (e.g., AnSwEr0001). The answer evaluator and the input field never connect.
Wrong:
ANS(str_cmp($answer)); # Looks for AnSwEr0001 # But JS or HTML creates an input with a different name -- no connection!
Right:
BEGIN_TEXT
\{ MODES(HTML => '<div style="display:none;">' . ans_rule(20) . '</div>', TeX => '') \}
END_TEXT
ANS(str_cmp($answer));
The rule: ans_rule() creates the input field that ANS() checks. Always create the ans_rule() first, then call ANS(). When you need a custom UI with JavaScript, hide the ans_rule() input and use JavaScript to populate its value.
RDKit and SMILES chemistry errors
If your problems use RDKit to render molecular structures from SMILES strings, incorrect SMILES notation can cause silent rendering failures or chemically wrong diagrams.
Incorrect formal charges in ring systems
Symptom: Molecule renders with wrong structure, or RDKit rejects the SMILES string entirely.
Cause: Using incorrect formal charge notation for nitrogen atoms in ring systems like imidazole. Writing NH2+ implies two hydrogens on one nitrogen, which is chemically incorrect for the imidazole ring.
Wrong:
# NH2+ implies TWO hydrogens on one nitrogen -- wrong for imidazole $imidazole_protonated = '...N=C[NH2+]1)...';
Right:
# Imidazole has two nitrogens: one NH, one NH+ $imidazole_protonated = 'C1=C([NH]C=[NH+]1)...';
Prevention: Always validate SMILES strings with Python RDKit before embedding them in PG problems. A SMILES string that parses without errors can still be chemically wrong, so check the rendered structure visually.
Debugging strategies
When the quick reference table below does not solve your problem, use these strategies to isolate the issue systematically.
Check line numbers
When variables seem empty or undefined, check where the variable is used versus where it is defined. Find the usage with a search for the variable name, then find the definition (the line with $var_name = ...). The definition line number must be lower (earlier in the file) than the usage line number.
Isolate PGML errors with binary search
When you get '*' was not closed or similar PGML parsing errors:
- Comment out the bottom half of your PGML block.
- Render the problem. If the error is gone, the problem is in the bottom half.
- Restore the bottom half and comment out the top half to confirm.
- Keep splitting the offending half until you find the exact line.
- Check for
*,**, or_near variables on that line.
Test with a minimal example
Create the smallest possible problem file that reproduces the error:
DOCUMENT();
loadMacros("PGstandard.pl", "PGML.pl");
$var = "test";
BEGIN_PGML
Test: [$var]
END_PGML
ENDDOCUMENT();
Add complexity one piece at a time until the problem reappears. The last piece you added is the cause.
Check renderer output
If your local setup supports the -r flag on the renderer script, use it to inspect the actual HTML output. Look for HTML that was escaped when it should not have been (< instead of <), missing elements, or structural problems in the generated markup.
Prevention checklist
Before committing a new PG problem file, verify each of these items:
- Variables are defined before use (check line numbers).
- No
myon variables referenced in PGML ($rb,$popup, display variables). - No
\@or\%references. Use[@array]or~~@arrayinstead. - No
usestatements. UseloadMacros()for PG macros. - No
*or**immediately around[$variables]in PGML. - HTML variables use
[$var]*syntax (with the asterisk). - No Perl loops inside
BEGIN_TEXT...END_TEXTblocks. - Hash keys sorted before random selection (see Advanced Randomization Techniques).
- Shuffling happens after all items are defined (see Advanced Randomization Techniques).
ans_rule()created beforeANS()call.- SMILES strings validated with RDKit (if applicable).
- No PGML parsing errors in renderer output.
Quick reference: error message table
Use this table to look up the error or symptom you are seeing. Find your row and follow the fix in the right column.
| Error message or symptom | Likely cause | Fix |
|---|---|---|
'*' was not closed |
Asterisks around variable in PGML | Remove * or use ** (bold) |
'**' was not closed |
Bold markers around variable | Remove bold or build emphasis in a variable |
| Empty output / no content | Variable used before defined | Move definition before usage |
| HTML shown literally | Missing * in [$var]* |
Add asterisk: [$var]* |
| Answer always wrong | Answer field name mismatch | Use ans_rule() before ANS() |
| RadioButtons renders as text input | my $rb = RadioButtons(...) |
Remove my: $rb = RadioButtons(...) |
Not an ARRAY reference |
\@array backslash ref |
Use [@array] or ~~@array |
Not a HASH reference |
\%hash backslash ref |
Use {%hash} or ~~%hash |
'require' trapped by operation mask |
use Module in PG |
Use loadMacros() or built-ins |
'eval "string"' trapped |
eval $code in PG |
Restructure to avoid string eval |
'open' trapped by operation mask |
File I/O in PG | Not possible; use PG data macros |
| Variable shows empty in PGML | my $var instead of $var |
Remove my |
| Random order changes between runs | Hash keys not sorted | Sort keys before selection |
| Table content disappears | <table> tags blocked |
Use niceTables.pl |
Number 1 appears instead of HTML |
MODES in eval context | Check MODES usage; use [$var]* instead |
A. parsed as a list item |
Ordered list auto-parsing | Use *A.* or A\. to escape |
Apply it today
When something breaks, come back to this page and look up the symptom. Most issues fall into one of these categories:
- A PGML parsing error caused by asterisks near variables (remove or change the markers).
- A variable that PGML cannot see because of
my(removemy). - HTML showing as literal text (add
*after the bracket). - A missing macro (add it to
loadMacros()). - A blocked HTML tag (switch to
niceTables.pl). - Variables used before they are defined (move the definition earlier).
- A backslash reference that does not work in the sandbox (use
[@array]). - Answer field name mismatches (create
ans_rule()beforeANS()). - SMILES chemistry errors in RDKit problems (validate with Python RDKit).
Start with the quick reference table above. Match your error message to the row, read the fix, and apply it. If your problem is still not working, use the debugging strategies earlier on this page to isolate the issue: check line numbers, use binary search on PGML blocks, test with a minimal example, and inspect the renderer output.
This page titled 7.4: Common Mistakes and How to Fix Them was last modified on Fri, 27 Feb 2026 15:39:27 GMT and is shared under a CC BY 4.0 license and was authored, remixed, and/or curated by Neil R. Voss.
The book could not be loaded just now.