1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-07 17:44:22 +03:00
inform7/services/html-module/Chapter 2/Documentation References.w

353 lines
11 KiB
OpenEdge ABL

[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("<i>See the manual: %w &gt; %w</i>", chap, sec);
} else {
WRITE("<i>See the manual.</i>");
}
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.
=
<documentation-symbol-tail> ::=
... ( <documentation-symbol> ) | ==> { pass 1 }
... -- <documentation-symbol> -- ==> { pass 1 }
<documentation-symbol> ::=
documented at ### ==> { Wordings::first_wn(WR[1]), - }
@ =
wording DocReferences::position_of_symbol(wording *W) {
if (<documentation-symbol-tail>(*W)) {
*W = GET_RW(<documentation-symbol-tail>, 1);
return Wordings::one_word(<<r>>);
}
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("&nbsp;"); 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("&nbsp;%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("&nbsp;"); 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("&nbsp;%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) {
@<Load in the documentation fragments file@>;
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; i<dr->fragment_length; i++) PUT((inchar32_t) p[i]);
}
}
@
@d MAX_EXTENT_OF_FRAGMENTS 256*1024
@<Load in the documentation fragments file@> =
FILE *FRAGMENTS = Filenames::fopen(
InstalledFiles::filename(DOCUMENTATION_SNIPPETS_IRES), "r");
if (FRAGMENTS) {
char *p = Memory::malloc(MAX_EXTENT_OF_FRAGMENTS, DOC_FRAGMENT_MREASON);
@<Scan the file into memory, translating from UTF-8@>;
@<Work out where the documentation fragments occur@>;
fclose(FRAGMENTS);
}
@ We scan to one long C string:
@<Scan the file into memory, translating from UTF-8@> =
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;
}
@<Work out where the documentation fragments occur@> =
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;
@<Complain about a bad documentation reference@>;
return NULL;
}
@ You and I could write a bad reference:
@<Complain about a bad documentation 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");
}