[DocReferences::] Documentation References. To enable index or results pages to link into documentation. @ Documentation is arranged in a series of HTML pages identified by section number 0, 1, 2, ..., and the index contains little blue help icons which link into this. In order to give these links the correct destinations, Inform needs to know which section number contains what: but section numbering moves around a lot as the documentation is written. To avoid needlessly recompiling Inform when documentation changes, we give certain sections aliases called "symbols" which are rather more lasting than the section numbering. These are read in from a file of cross-references generated by Indoc. = typedef struct documentation_ref { struct text_stream *doc_symbol; /* Reference is by this piece of text */ int section; /* HTML page number */ int used_already; /* Has this been used in a problem message already? */ int usage_count; /* For statistical purposes */ char *fragment_at; /* Pointer to HTML documentation fragment in memory */ int fragment_length; /* Number of bytes of fragment */ int sr_usage_count; int ext_usage_count; inchar32_t *chapter_reference; /* Or |NULL| if no chapter name supplied */ inchar32_t *section_reference; /* Or |NULL| if no section name supplied */ CLASS_DEFINITION } documentation_ref; @ @d DOCUMENTATION_REFERENCE_PROBLEMS_CALLBACK DocReferences::show_xref_in_problem = void DocReferences::show_xref_in_problem(text_stream *OUT, text_stream *sigil) { inchar32_t *chap = NULL, *sec = NULL; inchar32_t *leaf = DocReferences::link_if_possible_once( sigil, &chap, &sec); if (leaf) { HTML::open_indented_p(OUT, 2, "tight"); HTML_OPEN_WITH("a", "href=inform:/%w.html", leaf); HTML_TAG_WITH("img", "border=0 src=inform:/doc_images/help.png"); HTML_CLOSE("a"); WRITE(" "); if ((chap) && (sec)) { WRITE("See the manual: %w > %w", chap, sec); } else { WRITE("See the manual."); } HTML_CLOSE("p"); } } @ The blue query icons link to pages in the documentation, as described above. Documentation references are used to match the documentation text against the compiler so that each can be changed independently of the other. First, here's the code to read the Indoc-generated cross-references. The file is read on demand; in some runs, it won't be needed. = int xrefs_read = FALSE; void DocReferences::read_xrefs(void) { if (xrefs_read == FALSE) { xrefs_read = TRUE; TextFiles::read( InstalledFiles::filename(DOCUMENTATION_XREFS_IRES), TRUE, NULL, FALSE, DocReferences::read_xrefs_helper, NULL, NULL); } } void DocReferences::read_xrefs_helper(text_stream *line, text_file_position *tfp, void *unused_state) { WRITE_TO(line, "\n"); wording W = Feeds::feed_text(line); if (Wordings::length(W) < 2) return; int from = -1; LOOP_THROUGH_WORDING(i, W) { if (Lexer::word(i) == UNDERSCORE_V) from = i+1; } if (from == -1) internal_error("malformed cross-references file"); inchar32_t *chap = NULL, *sect = NULL; if ((Wordings::last_wn(W) >= from+1) && (Vocabulary::test_flags(from+1, TEXT_MC))) { Word::dequote(from+1); chap = Lexer::word_text(from+1); } if ((Wordings::last_wn(W) >= from+2) && (Vocabulary::test_flags(from+2, TEXT_MC))) { Word::dequote(from+2); sect = Lexer::word_text(from+2); } LOOP_THROUGH_WORDING(i, W) { if (i == from) break; documentation_ref *dr = CREATE(documentation_ref); dr->doc_symbol = Str::new(); WRITE_TO(dr->doc_symbol, "%+W", Wordings::one_word(i)); dr->section = from; dr->used_already = FALSE; dr->usage_count = 0; dr->sr_usage_count = 0; dr->ext_usage_count = 0; dr->chapter_reference = chap; dr->section_reference = sect; dr->fragment_at = NULL; dr->fragment_length = 0; } } @ The following routine is used to verify that a given text is, or is not, a valid documentation reference symbol. (For instance, we might look up |kind_vehicle| to see if any section of documentation has been flagged as giving information on vehicles.) If our speculative link symbol exists, we return the leafname for this documentation page, without filename extension (say |doc24|); if it does not exist, we return NULL. = int DocReferences::validate_if_possible(text_stream *temp) { DocReferences::read_xrefs(); documentation_ref *dr; LOOP_OVER(dr, documentation_ref) if (Str::eq(dr->doc_symbol, temp)) return TRUE; return FALSE; } @ And similarly, returning the page we link to: = inchar32_t *DocReferences::link_if_possible_once(text_stream *temp, inchar32_t **chap, inchar32_t **sec) { DocReferences::read_xrefs(); documentation_ref *dr; LOOP_OVER(dr, documentation_ref) if (Str::eq(dr->doc_symbol, temp)) { if (dr->used_already == FALSE) { inchar32_t *leaf = Lexer::word_text(dr->section); *chap = dr->chapter_reference; *sec = dr->section_reference; LOOP_OVER(dr, documentation_ref) if (Wide::cmp(leaf, Lexer::word_text(dr->section)) == 0) dr->used_already = TRUE; return leaf; } } return NULL; } @ In the Standard Rules, a number of phrases (and other constructs) are defined along with markers to sections in the documentation: here we parse these markers, returning either the word number of the documentation symbol in question, or $-1$ if there is none. Since this is used only with the Standard Rules, which are in English, there's no point in translating it to other natural languages. = ::= ... ( ) | ==> { pass 1 } ... -- -- ==> { pass 1 } ::= documented at ### ==> { Wordings::first_wn(WR[1]), - } @ = wording DocReferences::position_of_symbol(wording *W) { if ((*W)) { *W = GET_RW(, 1); return Wordings::one_word(<>); } return EMPTY_WORDING; } @ It's convenient to associate a usage count to each symbol, since every built-in documented phrase has a symbol. Every time Inform successfully uses such a phrase, it increments the usage count by calling the following: = #ifdef CORE_MODULE void DocReferences::doc_mark_used(text_stream *symb, int at_word) { DocReferences::read_xrefs(); documentation_ref *dr; LOOP_OVER(dr, documentation_ref) { if (Str::eq(dr->doc_symbol, symb)) { if (at_word >= 0) { source_file *pos = Lexer::file_of_origin(at_word); inform_extension *loc = Extensions::corresponding_to(pos); if (loc == NULL) dr->usage_count++; else if (Extensions::is_standard(loc)) dr->sr_usage_count++; else dr->ext_usage_count++; } else dr->sr_usage_count++; return; } } internal_error("unable to update usage count"); } #endif @ The following dumps the result. This is not useful for a single run, especially, but to be accumulated over a whole corpus of source texts, e.g.: = (text as ConsoleText) $ intest/Tangled/intest --keep-log=USAGE -log=phrase-usage examples = = void DocReferences::log_statistics(void) { LOG("The following shows how often each built-in phrase was used:\n"); DocReferences::read_xrefs(); documentation_ref *dr; LOOP_OVER(dr, documentation_ref) if (Str::begins_with_wide_string(dr->doc_symbol, U"ph")) LOG("USAGE: %S %d %d %d\n", dr->doc_symbol, dr->usage_count, dr->sr_usage_count, dr->ext_usage_count); } @ Finally, the blue "see relevant help page" icon links are placed by the following routine. = void DocReferences::link_to(OUTPUT_STREAM, text_stream *fn, int full) { documentation_ref *dr = DocReferences::name_to_dr(fn); if (dr) { if (full >= 0) WRITE(" "); else WRITE(" "); HTML_OPEN_WITH("a", "href=inform:/%N.html", dr->section); HTML_TAG_WITH("img", "border=0 src=inform:/doc_images/help.png"); HTML_CLOSE("a"); if ((full > 0) && (dr->chapter_reference) && (dr->section_reference)) { WRITE(" %w. %w", dr->chapter_reference, dr->section_reference); } } } void DocReferences::link(OUTPUT_STREAM, text_stream *fn) { DocReferences::link_to_S(OUT, fn, FALSE); } void DocReferences::fully_link(OUTPUT_STREAM, text_stream *fn) { DocReferences::link_to_S(OUT, fn, TRUE); } void DocReferences::link_to_S(OUTPUT_STREAM, text_stream *fn, int full) { documentation_ref *dr = DocReferences::name_to_dr(fn); if (dr) { if (full >= 0) WRITE(" "); else WRITE(" "); HTML_OPEN_WITH("a", "href=inform:/%N.html", dr->section); HTML_TAG_WITH("img", "border=0 src=inform:/doc_images/help.png"); HTML_CLOSE("a"); if ((full > 0) && (dr->chapter_reference) && (dr->section_reference)) { WRITE(" %w. %w", dr->chapter_reference, dr->section_reference); } } } @h Fragments. These are short pieces of documentation, which |indoc| has copied into a special file so that we can paste them into the index at appropriate places. Note that if the file can't be found, or contains nothing germane, we fail safe by doing nothing at all -- not issuing any internal errors. = void DocReferences::doc_fragment(OUTPUT_STREAM, text_stream *fn) { DocReferences::doc_fragment_to(OUT, fn); } int fragments_loaded = FALSE; void DocReferences::doc_fragment_to(OUTPUT_STREAM, text_stream *fn) { if (fragments_loaded == FALSE) { @; fragments_loaded = TRUE; } documentation_ref *dr = DocReferences::name_to_dr(fn); if ((dr) && (dr->fragment_at)) { char *p = dr->fragment_at; int i; for (i=0; ifragment_length; i++) PUT((inchar32_t) p[i]); } } @ @d MAX_EXTENT_OF_FRAGMENTS 256*1024 @ = FILE *FRAGMENTS = Filenames::fopen( InstalledFiles::filename(DOCUMENTATION_SNIPPETS_IRES), "r"); if (FRAGMENTS) { char *p = Memory::malloc(MAX_EXTENT_OF_FRAGMENTS, DOC_FRAGMENT_MREASON); @; @; fclose(FRAGMENTS); } @ We scan to one long C string: @ = int i = 0; p[0] = 0; while (TRUE) { inchar32_t c = TextFiles::utf8_fgetc(FRAGMENTS, NULL, NULL); if (c == CH32EOF) break; if (c == 0xFEFF) continue; /* the Unicode BOM non-character */ if (i == MAX_EXTENT_OF_FRAGMENTS) break; p[i++] = (char) c; p[i] = 0; } @ = int i = 0; documentation_ref *tracking = NULL; for (i=0; p[i]; i++) { if ((p[i] == '*') && (p[i+1] == '=')) { i += 2; TEMPORARY_TEXT(rn) int j; for (j=0; p[i+j]; j++) { if ((p[i+j] == '=') && (p[i+j+1] == '*')) { i = i+j+1; tracking = DocReferences::name_to_dr(rn); if (tracking) tracking->fragment_at = p+i+1; break; } else { PUT_TO(rn, (inchar32_t) p[i+j]); } } DISCARD_TEXT(rn) } else if (tracking) tracking->fragment_length++; } @ This is a slow search, of course, but the number of DRs is relatively low, and we need to search fairly seldom: = documentation_ref *DocReferences::name_to_dr(text_stream *fn) { DocReferences::read_xrefs(); documentation_ref *dr; LOOP_OVER(dr, documentation_ref) if (Str::eq(dr->doc_symbol, fn)) return dr; @; return NULL; } @ You and I could write a bad reference: @ = if (problem_count == 0) { LOG("Bad ref was <%S>. Known references are:\n", fn); DocReferences::read_xrefs(); LOOP_OVER(dr, documentation_ref) LOG("%S = %+N\n", dr->doc_symbol, dr->section); internal_error("Bad index documentation reference"); }