7.4: Common Mistakes and How to Fix Them

The ADAPT WeBWorK Handbook · LibreTexts

0%

Contents

    Text

    Size
    100%
    Theme
    Typeface
    Line length
    Spacing

    From LibreTexts, under CC BY 4.0, as the source states it; its equations are written in TeX, which the PDF shows typeset. This page at LibreTexts.

    7.4: Common Mistakes and How to Fix Them

    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]);
    
    Right:: keyword, pgml visible?, and use for.
    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 &lt; 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

    Symptom table: missing macro errors reference.
    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:

    1. Comment out the bottom half of your PGML block.
    2. Render the problem. If the error is gone, the problem is in the bottom half.
    3. Restore the bottom half and comment out the top half to confirm.
    4. Keep splitting the offending half until you find the exact line.
    5. 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 (&lt; 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 my on variables referenced in PGML ($rb, $popup, display variables).
    • No \@ or \% references. Use [@array] or ~~@array instead.
    • No use statements. Use loadMacros() 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_TEXT blocks.
    • Hash keys sorted before random selection (see Advanced Randomization Techniques).
    • Shuffling happens after all items are defined (see Advanced Randomization Techniques).
    • ans_rule() created before ANS() 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.

    Quick reference: error message table reference.
    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 (remove my).
    • 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() before ANS()).
    • 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.