An overview of the problems module's role and abilities.


§1. Prerequisites. The problems module is a part of the Inform compiler toolset. It is presented as a literate program or "web". Before diving in:

§2. Problems and their sigils. The task of this module is to issue error messages which may be lengthy and quite verbal in nature, and to produce them attractively to the command line, in an HTML file reporting the result of a run, or both.

Each different problem message has a textual identifier, or "sigil". For example, PM_VerbUnknownMeaning is the sigil for the Inform problem message issued when the user defines a verb in a way it doesn't understand. Sigils are in practice referred to using a macro _p_ defined early on in Problems, Level 0.

Sigils are char * constants. The Inform modules use the following naming conventions for sigils:

Because sigils correspond to test case names, they also have to follow the conventions on test case naming: in particular, the suffix -G means "for the Glulx virtual machine only", and similarly for -Z.

§3. In general problems are a bad thing, of course, but a call to ProblemSigils::require tells problems that the parent tool positively wants to issue a given problem message. This is useful when testing Inform on bad source text to check that it reports the badness as we hope.

The parent can also configure problems by calling ProblemSigils::echo_sigils or ProblemSigils::crash_on_problems.

§4. The story of a non-fatal problem message. Suppose that the core module wants to issue a problem message: what happens?

This depends on how complicated it is. The problems system has three levels:

Most of the problems issued by Inform look like this:

    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_BadDesk),
        "Inform does not support standing desks",
        "and needs your laptop to be lower than your ribcage at all times.");

The sigil for this (hypothetical) problem message is PM_BadDesk, and note the use of the _p_ macro to refer to it: see Problems, Level 0 for more. The first piece of text is always produced, and the second added only on the first occurrence. core doesn't need to do anything more than make this one function call to Level 3, and problems does everything else.

But a significant number of problems are less standard in shape and are called "handmade", meaning that core has to call some Level 2 functions. For example:

    Problems::quote_source(1, current_sentence);
    Problems::quote_wording(2, W);
    StandardProblems::handmade_problem(Task::syntax_tree(), _p_(PM_ScottishPlay));
    Problems::issue_problem_segment(
        "In the sentence %1, it looks as if '%2' might be a reference to a "
        "theatrical work connected with Scotland.");
    Problems::issue_problem_end();

What happens, in sequence, is that we

§5. As this demonstrates, problem messages are expanded from prototypes using a printf-like formatting system. Unlike printf, though, where %s means a string and %d a number, here the escape codes do not indicate the type of the data: they are simply %1, %2, %3, ..., %9. This is to prevent horrendous crashes when type mismatches occur: using a pointer to a phrase when trying to print a source code reference, for instance.

The placeholders do not need to be used contiguously — if you want to use just %4 and %7, feel free.

Four further escape codes switch between problem message versions, as follows:

Note that the form is reset to %A when a new problem message begins, but not in between calls to Problems::issue_problem_segment: i.e., if one segment leaves things in %L, the next segment, if there is one, resumes that way.

For example, "You wrote %1: %Sagain, %2.%Lbut %2, %3" is the message text used by StandardProblems::sentence_problem.

                     "You wrote %1: %Sagain, %2.%Lbut %2, %3"
    on first use --> "You wrote %1: but %2, %3"
    subsequently --> "You wrote %1: again, %2."

Here the punctuation; %3 is expected to end with a full stop and %2 not to.

Finally, the escape %P means "paragraph break here", and is used for adding subsequent clarifications to long or complicated problems.

§6. Problems, Level 3 contains functions for standardly-shaped problems, then. A significant amount of this section also deals with internal errors, that is, failed assertions; while foundation provides the basic system for handling those — i.e., print and then exit the program — core redirects all internal errors to StandardProblems::internal_error_fn, which ensures that they pass through our problems machinery here, and are thus properly recorded in the HTML problems report in the Inform app (if that's what the user is using).

§7. Problems, Level 2 contains functions for making quotations to fill the placeholders with content — see problem_quotation.

The mechanism for determining whether an explanation has been given before is Problems::explained_before. The obvious thing would be to go by the sigils of previously issued messages, but it actually uses the textual token supplied on the call to Problems::issue_problem_begin, which allows for some variations — Level 3 functions are able to use this to ensure that particular kinds of message are always, or are never, explained.

As Level 2 generates problem text, it calls down into ProblemBuffer::output_problem_buffer at Level 1.

Just a few functions at Level 2 issue fatal errors — that is, problems which cause an immediate exit of the program as soon as they are issued, and are typically used for filing-system disasters or failed assertions (so-called "internal errors").

Facilities for these are very limited:

These routines have to be written with care because a file-system disaster might mean that the problems file itself cannot be written to.

§8. Problems, Level 1 is concerned with the "problem buffer" PBUFF. This is a text used to hold the problem message as it is assembled from pieces, and only Level 2 functions should print to it. Even they should call down to two Level 1 functions when they want to write something other than straightforward text:

§9. When a portion of text has been buffered which Level 2 wants to get shot of, it calls down to ProblemBuffer::output_problem_buffer to send this to a file. By default, the text is actually sent two ways: to the standard console output, and to the debugging log (if there is one). But this can be diverted with ProblemBuffer::redirect_problem_stream, telling it to send problem text just one way.