[Problems::] Problems, Level 2. To assemble and format problem messages within the problem buffer. @ Problem messages begin with an indication of where in the source text the problem occurs, in terms of headings and subheadings written in the source text, and it is this indication that we consider first. For example, >> In Part the First, Chapter 1 - Attic Area: There can be up to 10 levels in the hierarchy of headings and subheadings, with level 0 the top level and level 9 the lowest. When we need to issue a problem at sentence |S|, we work out what the current heading is (if any) at each of the 10 levels. We do this by trekking right through the whole linked list of sentences until we reach |S|, changing the current headings whenever we pass one. This sounds inefficient, but of course few problems are issued, and in any case we cannot optimise by simply cacheing the heading level from one problem to the next because it is not true that problems are always issued in source code order. @default NO_HEADING_LEVELS 10 = void Problems::find_headings_at(parse_node_tree *T, parse_node *sentence, parse_node **problem_headings) { for (int i=0; i> In Chapter 2 - Cellar Area: omitting to mention "Part the First" this time, since that part has not changed. (And we never print internally made level 0, File, headings.) = parse_node *last_problem_headings[NO_HEADING_LEVELS]; @ Now for the actual displaying of the location text. The outcome image is a trickier thing to get right than might appear. By being in this routine, we know that a problem has been issued: the run will therefore not have been a success, and we can issue the "Inform failed" outcome image. A success image cannot be placed until right at the end of the run, when all possibility of problems has been passed: so there's no single point in Inform where a single line of code could choose between the two possibilities. = int do_not_locate_problems = FALSE; void Problems::show_problem_location(parse_node_tree *T) { parse_node *problem_headings[NO_HEADING_LEVELS]; int i, f = FALSE; if (problem_count == 0) { #ifdef FIRST_PROBLEMS_CALLBACK FIRST_PROBLEMS_CALLBACK(problems_file); #endif for (i=0; i; break; } for (i=0; i = source_file *pos = NULL; ProblemBuffer::clear(); if (problem_count > 0) WRITE_TO(PBUFF, ">---> "); WRITE_TO(PBUFF, "In"); for (f=FALSE; i 10)) internal_error("problem quotation number out of range"); problem_quotations[t].structure_quoted = v; problem_quotations[t].expander = f; problem_quotations[t].quotation_type = '?'; problem_quotations[t].wording_based = FALSE; problem_quotations[t].text_quoted = EMPTY_WORDING; } void Problems::problem_quote_tinted(int t, void *v, void (*f)(text_stream *, void *), char type) { if ((t<0) || (t > 10)) internal_error("problem quotation number out of range"); problem_quotations[t].structure_quoted = v; problem_quotations[t].expander = f; problem_quotations[t].quotation_type = type; problem_quotations[t].wording_based = FALSE; problem_quotations[t].text_quoted = EMPTY_WORDING; } void Problems::problem_quote_textual(int t, char type, wording W) { if ((t<0) || (t > 10)) internal_error("problem quotation number out of range"); problem_quotations[t].structure_quoted = NULL; problem_quotations[t].quotation_type = type; problem_quotations[t].wording_based = TRUE; problem_quotations[t].text_quoted = W; } @ Here are the three public routines for quoting from text: either via a node in the parse tree, or with a literal word range. = void Problems::quote_source(int t, parse_node *p) { if (p == NULL) Problems::problem_quote_textual(t, 'S', EMPTY_WORDING); else Problems::quote_wording_as_source(t, Node::get_text(p)); } void Problems::quote_source_eliding_begin(int t, parse_node *p) { if (p == NULL) Problems::problem_quote_textual(t, 'S', EMPTY_WORDING); else Problems::problem_quote_textual(t, 'S', Node::get_text(p)); } void Problems::quote_wording(int t, wording W) { Problems::problem_quote_textual(t, 'W', W); } void Problems::quote_wording_tinted_green(int t, wording W) { Problems::problem_quote_textual(t, 'g', W); } void Problems::quote_wording_tinted_red(int t, wording W) { Problems::problem_quote_textual(t, 'r', W); } void Problems::quote_wording_as_source(int t, wording W) { Problems::problem_quote_textual(t, 'S', W); } void Problems::quote_text(int t, char *p) { Problems::problem_quote(t, (void *) p, Problems::expand_text); } void Problems::expand_text(OUTPUT_STREAM, void *p) { WRITE("%s", (char *) p); } void Problems::quote_wide_text(int t, wchar_t *p) { Problems::problem_quote(t, (void *) p, Problems::expand_wide_text); } void Problems::expand_wide_text(OUTPUT_STREAM, void *p) { WRITE("%w", (wchar_t *) p); } void Problems::quote_stream(int t, text_stream *p) { Problems::problem_quote(t, (void *) p, Problems::expand_stream); } void Problems::quote_stream_tinted_green(int t, text_stream *p) { Problems::problem_quote_tinted(t, (void *) p, Problems::expand_stream, 'g'); } void Problems::quote_stream_tinted_red(int t, text_stream *p) { Problems::problem_quote_tinted(t, (void *) p, Problems::expand_stream, 'r'); } void Problems::expand_stream(OUTPUT_STREAM, void *p) { WRITE("%S", (text_stream *) p); } void Problems::quote_wa(int t, word_assemblage *p) { Problems::problem_quote(t, (void *) p, Problems::expand_wa); } void Problems::expand_wa(OUTPUT_STREAM, void *p) { WRITE("%A", (word_assemblage *) p); } void Problems::expand_text_within_reason(OUTPUT_STREAM, wording W) { W = Wordings::truncate(W, QUOTATION_TOLERANCE_LIMIT); WRITE("%++>"); this_is_a_subsequent_use_of_problem = FALSE; } else if (strcmp(message, "****") == 0) { WRITE_TO(PBUFF, ">++++>"); this_is_a_subsequent_use_of_problem = FALSE; } else if (strcmp(message, "***") == 0) { WRITE_TO(PBUFF, ">+++>"); this_is_a_subsequent_use_of_problem = FALSE; } else if (strcmp(message, "**") == 0) { this_is_a_subsequent_use_of_problem = FALSE; } else { if (T) Problems::show_problem_location(T); problem_count++; WRITE_TO(PBUFF, ">--> "); this_is_a_subsequent_use_of_problem = Problems::explained_before(message); } scanning_problem_for = ANY_USE_OF_PROBLEM; } void Problems::issue_problem_end(void) { #ifdef ENDING_MESSAGE_PROBLEMS_CALLBACK ENDING_MESSAGE_PROBLEMS_CALLBACK(); #endif ProblemBuffer::output_problem_buffer(1); Problems::problem_documentation_links(problems_file); if (crash_on_all_problems) ProblemSigils::force_crash(); } @ Documentation links: = void Problems::problem_documentation_links(OUTPUT_STREAM) { if (Str::len(sigil_of_latest_unlinked_problem) == 0) return; #ifdef DOCUMENTATION_REFERENCE_PROBLEMS_CALLBACK DOCUMENTATION_REFERENCE_PROBLEMS_CALLBACK(OUT, sigil_of_latest_unlinked_problem); #endif Str::clear(sigil_of_latest_unlinked_problem); } text_stream *Problems::latest_sigil(void) { return sigil_of_latest_problem; } @h Appending source. = wording appended_source = EMPTY_WORDING; void Problems::append_source(wording W) { appended_source = W; } void Problems::transcribe_appended_source(void) { if (Wordings::nonempty(appended_source)) ProblemBuffer::copy_source_reference(appended_source); } @h Issuing a segment of a problem message. This function performs the substitution of quotations into problem messages and sends them on their way: which is called //Problems::issue_problem_segment// since it only appends a further piece of text, and may be used several times to build up complicated messages. = void Problems::issue_problem_segment(char *message) { for (int i=0; message[i]; i++) { if (message[i] == '%') { switch (message[i+1]) { case 'A': scanning_problem_for = ANY_USE_OF_PROBLEM; i++; continue; case 'L': scanning_problem_for = FIRST_USE_OF_PROBLEM; i++; continue; case 'S': scanning_problem_for = SUBSEQUENT_USE_OF_PROBLEM; i++; continue; } } if ((scanning_problem_for == SUBSEQUENT_USE_OF_PROBLEM) && (this_is_a_subsequent_use_of_problem == FALSE)) continue; if ((scanning_problem_for == FIRST_USE_OF_PROBLEM) && (this_is_a_subsequent_use_of_problem == TRUE)) continue; @; } } @ Ordinarily we just append the new character, but we also act on the escapes |%P| and |%1| to |%9|. |%P| forces a paragraph break, or at any rate, it does in the eventual HTML version of the problem message. Note that these escapes are acted on only if they occur in a contextually allowed part of the problem message (e.g., if they occur in the short form only, they will only be acted on when the shortened form is the one being issued). @ = if (message[i] == '%') { switch (message[i+1]) { case 'P': PUT_TO(PBUFF, FORCE_NEW_PARA_CHAR); i++; continue; case '%': PUT_TO(PBUFF, '%'); i++; continue; } if (Characters::isdigit((wchar_t) message[i+1])) { int t = ((int) (message[i+1]))-((int) '0'); i++; if ((t>=1) && (t<=9)) { if (problem_quotations[t].wording_based) @ else @ } continue; } } PUT_TO(PBUFF, message[i]); @ This is where a quotation escape, such as |%2|, is expanded: by looking up its type, stored internally as a single character. @ = switch(problem_quotations[t].quotation_type) { /* Monochrome wording */ case 'S': ProblemBuffer::copy_source_reference( problem_quotations[t].text_quoted); break; case 'W': ProblemBuffer::copy_text( problem_quotations[t].text_quoted); break; /* Tinted wording */ case 'r': @; break; case 'g': @; break; default: internal_error("unknown error token type"); } @ Tinting text involves some HTML, of course: @ = TEMPORARY_TEXT(OUT) HTML::begin_colour(OUT, I"800000"); WRITE("%W", problem_quotations[t].text_quoted); HTML::end_colour(OUT); @; DISCARD_TEXT(OUT) @ And: @ = TEMPORARY_TEXT(OUT) HTML::begin_colour(OUT, I"008000"); WRITE("%W", problem_quotations[t].text_quoted); HTML::end_colour(OUT); @; DISCARD_TEXT(OUT) @ More generally, the reference is to some structure we can't write ourselves, and must delegate to: @ = Problems::append_source(EMPTY_WORDING); TEMPORARY_TEXT(OUT) if (problem_quotations[t].quotation_type == 'r') HTML::begin_colour(OUT, I"800000"); if (problem_quotations[t].quotation_type == 'g') HTML::begin_colour(OUT, I"008000"); (problem_quotations[t].expander)(OUT, problem_quotations[t].structure_quoted); if (problem_quotations[t].quotation_type == 'r') HTML::end_colour(OUT); if (problem_quotations[t].quotation_type == 'g') HTML::end_colour(OUT); @; DISCARD_TEXT(OUT) Problems::transcribe_appended_source(); @ = LOOP_THROUGH_TEXT(pos, OUT) { wchar_t c = Str::get(pos); if (c == '<') c = PROTECTED_LT_CHAR; if (c == '>') c = PROTECTED_GT_CHAR; PUT_TO(PBUFF, c); } @ This version is much shorter, since escapes aren't allowed: = void Problems::issue_problem_segment_from_stream(text_stream *message) { WRITE_TO(PBUFF, "%S", message); } @h Fatalities. = void Problems::fatal(char *message) { WRITE_TO(STDERR, message); WRITE_TO(STDERR, "\n"); STREAM_FLUSH(STDERR); if (crash_on_all_problems) ProblemSigils::force_crash(); ProblemSigils::exit(2); } void Problems::fatal_on_file(char *message, filename *F) { WRITE_TO(STDERR, message); WRITE_TO(STDERR, "\nOffending filename: <%f>\n", F); STREAM_FLUSH(STDERR); if (crash_on_all_problems) ProblemSigils::force_crash(); ProblemSigils::exit(2); }