1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-05 16:44:21 +03:00
inform7/services/problems-module/Preliminaries/What This Module Does.w
2020-05-21 00:11:29 +01:00

193 lines
9.7 KiB
OpenEdge ABL

What This Module Does.
An overview of the problems module's role and abilities.
@h Prerequisites.
The problems module is a part of the Inform compiler toolset. It is
presented as a literate program or "web". Before diving in:
(a) It helps to have some experience of reading webs: see //inweb// for more.
(b) The module is written in C, in fact ANSI C99, but this is disguised by the
fact that it uses some extension syntaxes provided by the //inweb// literate
programming tool, making it a dialect of C called InC. See //inweb// for
full details, but essentially: it's C without predeclarations or header files,
and where functions have names like |Tags::add_by_name| rather than |add_by_name|.
(c) This module uses other modules drawn from the //compiler//, and also
uses a module of utility functions called //foundation//.
For more, see //foundation: A Brief Guide to Foundation//.
@h 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:
(a) A problem which is thought never to be generated has the sigil
|BelievedImpossible|. Inform is quite defensively coded, so there are several
dozen of these -- they are safety nets to catch cases we didn't think of.
(b) A problem which either cannot be tested by //intest//, or is just
impracticable to do so, has the sigil |Untestable|.
(c) A problem which can be tested, but for which nobody has yet written a
test case, has the sigil |...|.
(d) Otherwise a problem should have a unique alphanumeric name beginning with
|PM_|, for "problem message": for example, |PM_NoSuchHieroglyph|. This should
be the same name as that of the test case which exercises it.
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|.
@ 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//.
@h 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:
(3) //Problems, Level 3// contains functions for problem messages which have
a commonly-needed shape to them: for example, //StandardProblems::sentence_problem//.
The functions in question call down to...
(2) //Problems, Level 2//, which contains functions to accept "quotations" and
a problem message with placeholders in to hold those quotations; after which,
they call down to...
(1) //Problems, Level 1//, where the text of a message is stored in the
"problem buffer" and eventually printed or written to a file.
Most of the problems issued by Inform look like this:
= (text as InC)
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:
= (text as InC)
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
(a) Establish what material should go into the placeholders |%1| and |%2|,
(b) Call //StandardProblems::handmade_problem// to begin work,
(c) Call //Problems::issue_problem_segment// a number of times -- though often
just once -- to put some text into the problem, and
(d) Call //Problems::issue_problem_end// to signal that we are done.
@ 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:
(*) |%L| means "long form", the version used the first time this message is
generated,
(*) |%S| means "short form", for subsequent times,
(*) |%A| means "both long and short", which is the situation at the start,
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//.
= (text)
"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 "poragraph break here", and is used for adding
subsequent clarifications to long or complicated problems.
@ //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).
@ //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:
(*) //Problems::fatal// for a simple message with fixed wording.
(*) //Problems::fatal_on_file// for a message relating to a file.
These routines have to be written with care because a file-system disaster
might mean that the problems file itself cannot be written to.
@ //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:
(*) Source text from the lexer can be quoted into it with //ProblemBuffer::copy_text//,
which automatically trims excessive quotes for length.
(*) References to positions in the source text can be inserted with
//ProblemBuffer::copy_source_reference//: there is a sort of protocol for how
this is done, with use of the magic |SOURCE_REF_CHAR| which will be
intercepted later on when the problems file is written out as HTML (see
//foundation: HTML// for details).
@ 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 three ways: to the standard
console output, to the debugging log (if there is one), and to the telemetry
file (if there is one). But this can be diverted with
//ProblemBuffer::redirect_problem_stream//, telling it to send problem text
just one way.
@h Telemetry.
The //Telemetry// system isn't really to do with problems, except that it
can log them; it is an optional facility to log activity of a tool or app.
This is locally stored rather than sent over any wires, so perhaps there's
no "tele-" about it.