1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-06-26 04:00:43 +03:00

Started in on a better extension documentation renderer

This commit is contained in:
Graham Nelson 2023-07-17 00:44:11 +01:00
parent fa73c07445
commit ad2a8e18d8
35 changed files with 705 additions and 408 deletions

View file

@ -1,6 +1,6 @@
# Inform 7
[Version](notes/versioning.md): 10.2.0-beta+6W83 'Krypton' (16 July 2023)
[Version](notes/versioning.md): 10.2.0-beta+6W84 'Krypton' (17 July 2023)
## About Inform

View file

@ -1,3 +1,3 @@
Prerelease: beta
Build Date: 16 July 2023
Build Number: 6W83
Build Date: 17 July 2023
Build Number: 6W84

View file

@ -574,9 +574,9 @@ pathname *Supervisor::installed_files(void) {
return Supervisor::default_internal_path();
}
@ As noted above, the transient area is used for ephemera such as dynamically
written documentation and telemetry files. |-transient| sets it, but otherwise
the external nest is used.
@ The transient area can be used for build files for project files, where
there's no build directory provided by the project bundle. |-transient| sets
it, but otherwise the external nest is used.
=
pathname *Supervisor::transient(void) {

View file

@ -15,6 +15,13 @@ which use this module:
@e build_skill_CLASS
@e build_step_CLASS
@e build_vertex_CLASS
@e cdoc_heading_CLASS
@e cdoc_example_CLASS
@e cdoc_passage_CLASS
@e cdoc_paragraph_CLASS
@e cdoc_code_sample_CLASS
@e cdoc_code_line_CLASS
@e compiled_documentation_CLASS
@e control_structure_phrase_CLASS
@e copy_error_CLASS
@e element_activation_CLASS
@ -45,7 +52,14 @@ DECLARE_CLASS(build_script)
DECLARE_CLASS(build_skill)
DECLARE_CLASS(build_step)
DECLARE_CLASS(build_vertex)
DECLARE_CLASS(cdoc_heading)
DECLARE_CLASS(cdoc_example)
DECLARE_CLASS(cdoc_passage)
DECLARE_CLASS(cdoc_paragraph)
DECLARE_CLASS(cdoc_code_sample)
DECLARE_CLASS(cdoc_code_line)
DECLARE_CLASS(control_structure_phrase)
DECLARE_CLASS(compiled_documentation)
DECLARE_CLASS(copy_error)
DECLARE_CLASS(element_activation)
DECLARE_CLASS(extensions_key_item)

View file

@ -14,7 +14,7 @@ typedef struct inform_extension {
struct inbuild_copy *as_copy;
struct wording body_text; /* Body of source text supplied in extension, if any */
int body_text_unbroken; /* Does this contain text waiting to be sentence-broken? */
struct wording documentation_text; /* Documentation supplied in extension, if any */
struct compiled_documentation *documentation; /* or |NULL| if none supplied */
int documentation_sought; /* Has it yet been looked for? */
int standard; /* the (or perhaps just a) Standard Rules extension */
int authorial_modesty; /* Do not credit in the compiled game */
@ -74,7 +74,7 @@ void Extensions::scan(inbuild_copy *C) {
@<Initialise the extension docket@> =
E->body_text = EMPTY_WORDING;
E->body_text_unbroken = FALSE;
E->documentation_text = EMPTY_WORDING;
E->documentation = NULL;
E->documentation_sought = FALSE;
E->standard = FALSE;
E->authorial_modesty = FALSE;
@ -777,6 +777,9 @@ void Extensions::read_source_text_for(inform_extension *E) {
SVEXPLAIN(1, "(from %f)\n", F);
DISCARD_TEXT(synopsis)
if (E->read_into_file) {
E->documentation =
DocumentationCompiler::compile(
TextFromFiles::torn_off_documentation(E->read_into_file), E);
E->read_into_file->your_ref = STORE_POINTER_inbuild_copy(E->as_copy);
@<Break the text into sentences@>;
E->body_text_unbroken = FALSE;
@ -810,38 +813,19 @@ then its sentences will go to the extension's own tree.
@<Break the text into sentences@> =
wording EXW = E->read_into_file->text_read;
if (Wordings::nonempty(EXW))
@<Break the extension's text into body and documentation@>;
E->body_text = EXW;
E->body_text_unbroken = TRUE; /* mark this to be sentence-broken */
inform_project *project = E->read_into_project;
if (project) E->syntax_tree = project->syntax_tree;
Sentences::break_into_extension_copy(E->syntax_tree,
E->body_text, E->as_copy, project);
E->body_text_unbroken = FALSE;
@ If an extension file contains the special text (outside literal mode) of
|---- Documentation ----| then this is taken as the end of the Inform source,
and the beginning of a snippet of documentation about the extension; text from
that point on is saved until later, but not broken into sentences for the
parse tree, and it is therefore invisible to the rest of Inform. If this
division line is not present then the extension contains only body source
and no documentation.
=
<extension-body> ::=
*** ---- documentation ---- ... | ==> { TRUE, - }
... ==> { FALSE, - }
@<Break the extension's text into body and documentation@> =
<extension-body>(EXW);
E->body_text = GET_RW(<extension-body>, 1);
if (<<r>>) E->documentation_text = GET_RW(<extension-body>, 2);
E->body_text_unbroken = TRUE; /* mark this to be sentence-broken */
@ In directory extensions, documentation can be stored separately:
=
wording Extensions::get_documentation_text(inform_extension *E) {
if (E == NULL) return EMPTY_WORDING;
compiled_documentation *Extensions::get_documentation(inform_extension *E) {
if (E == NULL) return NULL;
Copies::get_source_text(E->as_copy); /* in the unlikely event this has not happened yet */
if (E->documentation_sought == FALSE) {
if (E->as_copy->location_if_path) {
@ -851,23 +835,31 @@ wording Extensions::get_documentation_text(inform_extension *E) {
}
E->documentation_sought = TRUE;
}
return E->documentation_text;
return E->documentation;
}
@<Fetch wording from stand-alone file@> =
if (Wordings::nonempty(E->documentation_text)) {
if (E->documentation == NULL) {
TEMPORARY_TEXT(error_text)
WRITE_TO(error_text,
"this extension provides documentation both as a file and in its source");
Copies::attach_error(E->as_copy, CopyErrors::new_T(EXT_MISWORDED_CE, -1, error_text));
DISCARD_TEXT(error_text)
} else {
int state = SourceText::for_documentation_only(TRUE);
source_file *sf = SourceText::read_file(E->as_copy, F, NULL, FALSE);
if (sf) E->documentation_text = sf->text_read;
SourceText::for_documentation_only(state);
TEMPORARY_TEXT(temp)
TextFiles::read(F, FALSE, "unable to read file of extension documentation", TRUE,
&Extensions::read_extension_file_helper, NULL, temp);
E->documentation = DocumentationCompiler::compile(temp, E);
DISCARD_TEXT(temp)
}
@ =
void Extensions::read_extension_file_helper(text_stream *text, text_file_position *tfp,
void *v_state) {
text_stream *contents = (text_stream *) v_state;
WRITE_TO(contents, "%S\n", text);
}
@ When the extension source text was read from its |source_file|, we
attached a reference to say which |inform_extension| it was, and here we
make use of that:

View file

@ -0,0 +1,490 @@
[DocumentationCompiler::] Documentation Compiler.
To compile documentation from an extension into a useful internal format
for rendering out.
@
=
typedef struct compiled_documentation {
struct text_stream *original;
struct inform_extension *associated_extension;
struct heterogeneous_tree *content;
int empty;
CLASS_DEFINITION
} compiled_documentation;
tree_type *cdoc_tree_TT = NULL;
tree_node_type *heading_TNT = NULL, *example_TNT = NULL,
*passage_TNT = NULL, *paragraph_TNT = NULL, *code_sample_TNT = NULL, *code_line_TNT = NULL;
typedef struct cdoc_heading {
struct text_stream *count;
struct text_stream *name;
int level; /* 0 = root, 1 = chapter, 2 = section */
int ID;
CLASS_DEFINITION
} cdoc_heading;
tree_node *DocumentationCompiler::new_heading(heterogeneous_tree *tree,
text_stream *title, int level, int ID, int cc, int sc) {
cdoc_heading *H = CREATE(cdoc_heading);
H->count = Str::new();
if (cc > 0) WRITE_TO(H->count, "%d", cc);
if ((cc > 0) && (sc > 0)) WRITE_TO(H->count, ".");
if (sc > 0) WRITE_TO(H->count, "%d", sc);
H->name = Str::duplicate(title);
H->level = level;
H->ID = ID;
return Trees::new_node(tree, heading_TNT, STORE_POINTER_cdoc_heading(H));
}
typedef struct cdoc_example {
struct text_stream *name;
int star_count;
int number;
char letter;
CLASS_DEFINITION
} cdoc_example;
tree_node *DocumentationCompiler::new_example(heterogeneous_tree *tree,
text_stream *title, int star_count, int ecount) {
cdoc_example *E = CREATE(cdoc_example);
E->name = Str::duplicate(title);
E->star_count = star_count;
E->number = ecount;
E->letter = 'A' + (char) ecount - 1;
return Trees::new_node(tree, example_TNT, STORE_POINTER_cdoc_example(E));
}
typedef struct cdoc_passage {
CLASS_DEFINITION
} cdoc_passage;
tree_node *DocumentationCompiler::new_passage(heterogeneous_tree *tree) {
cdoc_passage *P = CREATE(cdoc_passage);
return Trees::new_node(tree, passage_TNT, STORE_POINTER_cdoc_passage(P));
}
typedef struct cdoc_paragraph {
struct text_stream *content;
CLASS_DEFINITION
} cdoc_paragraph;
tree_node *DocumentationCompiler::new_paragraph(heterogeneous_tree *tree,
text_stream *content) {
cdoc_paragraph *P = CREATE(cdoc_paragraph);
P->content = Str::duplicate(content);
return Trees::new_node(tree, paragraph_TNT, STORE_POINTER_cdoc_paragraph(P));
}
typedef struct cdoc_code_sample {
CLASS_DEFINITION
} cdoc_code_sample;
tree_node *DocumentationCompiler::new_code_sample(heterogeneous_tree *tree) {
cdoc_code_sample *C = CREATE(cdoc_code_sample);
return Trees::new_node(tree, code_sample_TNT, STORE_POINTER_cdoc_code_sample(C));
}
typedef struct cdoc_code_line {
struct text_stream *content;
int indentation;
CLASS_DEFINITION
} cdoc_code_line;
tree_node *DocumentationCompiler::new_code_line(heterogeneous_tree *tree,
text_stream *content, int indentation) {
cdoc_code_line *C = CREATE(cdoc_code_line);
C->content = Str::duplicate(content);
C->indentation = indentation;
return Trees::new_node(tree, code_line_TNT, STORE_POINTER_cdoc_code_line(C));
}
heterogeneous_tree *DocumentationCompiler::new_tree(void) {
if (cdoc_tree_TT == NULL) {
cdoc_tree_TT = Trees::new_type(I"documentation tree", &DocumentationCompiler::verify_root);
heading_TNT = Trees::new_node_type(I"heading", cdoc_heading_CLASS, &DocumentationCompiler::heading_verifier);
example_TNT = Trees::new_node_type(I"example", cdoc_example_CLASS, &DocumentationCompiler::example_verifier);
passage_TNT = Trees::new_node_type(I"passage", cdoc_passage_CLASS, &DocumentationCompiler::passage_verifier);
paragraph_TNT = Trees::new_node_type(I"paragraph", cdoc_paragraph_CLASS, &DocumentationCompiler::paragraph_verifier);
code_sample_TNT = Trees::new_node_type(I"code sample", cdoc_code_sample_CLASS, &DocumentationCompiler::code_sample_verifier);
code_line_TNT = Trees::new_node_type(I"line", cdoc_code_line_CLASS, &DocumentationCompiler::code_line_verifier);
}
heterogeneous_tree *tree = Trees::new(cdoc_tree_TT);
Trees::make_root(tree, DocumentationCompiler::new_heading(tree, I"(root)", 0, 0, 0, 0));
return tree;
}
int DocumentationCompiler::verify_root(tree_node *N) {
if ((N == NULL) || (N->type != heading_TNT) || (N->next))
return FALSE;
return TRUE;
}
int DocumentationCompiler::heading_verifier(tree_node *N) {
for (tree_node *C = N->child; C; C = C->next) {
if ((C->type != heading_TNT) && (C->type != example_TNT) && (C->type != passage_TNT))
return FALSE;
if ((C->type == passage_TNT) && (C->next)) return FALSE;
}
return TRUE;
}
int DocumentationCompiler::example_verifier(tree_node *N) {
if ((N->child == NULL) || (N->child->type != passage_TNT) || (N->child->next))
return FALSE;
return TRUE;
}
int DocumentationCompiler::passage_verifier(tree_node *N) {
for (tree_node *C = N->child; C; C = C->next)
if ((C->type != paragraph_TNT) && (C->type != code_sample_TNT))
return FALSE;
return TRUE;
}
int DocumentationCompiler::paragraph_verifier(tree_node *N) {
if (N->child) return FALSE; /* This must be a leaf node */
return TRUE;
}
int DocumentationCompiler::code_sample_verifier(tree_node *N) {
for (tree_node *C = N->child; C; C = C->next)
if (C->type != code_line_TNT)
return FALSE;
if (N->child == NULL) return FALSE;
return TRUE;
}
int DocumentationCompiler::code_line_verifier(tree_node *N) {
if (N->child) return FALSE; /* This must be a leaf node */
return TRUE;
}
@ =
void DocumentationCompiler::show_tree(text_stream *OUT, heterogeneous_tree *T) {
WRITE("%S\n", T->type->name);
WRITE("--------\n");
INDENT;
Trees::traverse_from(T->root, &DocumentationCompiler::visit, (void *) DL, 0);
OUTDENT;
WRITE("--------\n");
}
int DocumentationCompiler::visit(tree_node *N, void *state, int L) {
text_stream *OUT = (text_stream *) state;
for (int i=0; i<L; i++) WRITE(" ");
if (N->type == heading_TNT) {
cdoc_heading *H = RETRIEVE_POINTER_cdoc_heading(N->content);
WRITE("Heading H%d level %d: '%S'\n", H->ID, H->level, H->name);
} else if (N->type == example_TNT) {
cdoc_example *E = RETRIEVE_POINTER_cdoc_example(N->content);
WRITE("Example: '%S' (%d star(s))\n", E->name, E->star_count);
} else if (N->type == passage_TNT) {
WRITE("Passage\n");
} else if (N->type == paragraph_TNT) {
cdoc_paragraph *E = RETRIEVE_POINTER_cdoc_paragraph(N->content);
WRITE("Paragraph: %d chars\n", Str::len(E->content));
for (int i=0; i<L+1; i++) { INDENT; }
WRITE("%S\n", E->content);
for (int i=0; i<L+1; i++) { OUTDENT; }
} else if (N->type == code_sample_TNT) {
WRITE("Code sample\n");
} else if (N->type == code_line_TNT) {
cdoc_code_line *E = RETRIEVE_POINTER_cdoc_code_line(N->content);
WRITE("Code line: ");
for (int i=0; i<E->indentation; i++) WRITE(" ");
WRITE("%S\n", E->content);
} else WRITE("Unknown node\n");
return TRUE;
}
@
=
compiled_documentation *DocumentationCompiler::compile(text_stream *source,
inform_extension *associated_extension) {
if (Str::len(source) == 0) return NULL;
compiled_documentation *cd = CREATE(compiled_documentation);
cd->original = Str::duplicate(source);
cd->associated_extension = associated_extension;
cd->content = DocumentationCompiler::new_tree();
tree_node *current_headings[3], *current_holder = NULL,
*current_passage = cd->content->root, *current_paragraph = NULL, *current_code = NULL;
current_headings[0] = cd->content->root;
current_headings[1] = NULL;
current_headings[2] = NULL;
int pending_code_sample_blanks = 0, heading_ID = 1, ccount = 0, scount = 0, ecount = 0;
@<Parse the source linewise@>;
cd->empty = FALSE;
if (Str::is_whitespace(source)) cd->empty = TRUE;
SVEXPLAIN(1, "(compiling documentation: %d chars)\n", Str::len(source));
SVEXPLAIN(3, "(from source...)\n%S\n(...end of source)\n", source);
DocumentationCompiler::show_tree(DL, cd->content);
return cd;
}
@<Parse the source linewise@> =
TEMPORARY_TEXT(line)
int indentation = 0, space_count = 0;
for (int i=0; i<Str::len(source); i++) {
wchar_t c = Str::get_at(source, i);
if (c == '\n') {
@<Line read@>;
Str::clear(line);
indentation = 0; space_count = 0;
} else if ((Str::len(line) == 0) && (Characters::is_whitespace(c))) {
if (c == '\t') indentation++;
if (c == ' ') space_count++;
if (space_count == 4) { indentation++; space_count = 0; }
} else {
PUT_TO(line, c);
}
}
if (Str::len(line) > 0) @<Line read@>;
@<Complete passage@>;
DISCARD_TEXT(line)
@<Line read@> =
Str::trim_white_space(line);
if (Str::len(line) == 0) {
if (current_paragraph) @<Complete paragraph or code@>;
if (current_code) @<Insert line break in code@>;
} else if (indentation == 0) {
match_results mr = Regexp::create_mr();
if (Regexp::match(&mr, line, L"Section *: *(%c+?)")) {
scount++;
tree_node *s_node = DocumentationCompiler::new_heading(cd->content, mr.exp[0], 2, heading_ID++, ccount, scount);
if (current_headings[1] == NULL) LOG("*** No chapter for section '%S'***\n", mr.exp[0]);
Trees::make_child(s_node, current_headings[1]);
current_headings[2] = s_node;
@<Complete passage@>;
current_holder = s_node;
} else if (Regexp::match(&mr, line, L"Chapter *: *(%c+?)")) {
ccount++;
scount = 0;
tree_node *c_node = DocumentationCompiler::new_heading(cd->content, mr.exp[0], 1, heading_ID++, ccount, scount);
Trees::make_child(c_node, current_headings[0]);
current_headings[1] = c_node;
current_headings[2] = NULL;
@<Complete passage@>;
current_holder = c_node;
} else if (Regexp::match(&mr, line, L"Example *: *(%**) *(%c+?)")) {
ecount++;
tree_node *e_node = DocumentationCompiler::new_example(cd->content, mr.exp[1], Str::len(mr.exp[0]), ecount);
for (int j=2; j>=0; j--)
if (current_headings[j]) {
Trees::make_child(e_node, current_headings[j]);
break;
}
current_holder = e_node;
@<Complete passage@>;
} else {
if (current_paragraph == NULL) @<Begin paragraph@>;
@<Insert space in paragraph@>;
@<Insert line in paragraph@>;
}
Regexp::dispose_of(&mr);
} else {
if (current_code == NULL) @<Begin code@>
@<Insert line in code sample@>;
}
@<Begin passage@> =
if (current_passage == NULL) {
current_passage = DocumentationCompiler::new_passage(cd->content);
Trees::make_child(current_passage, current_holder);
current_paragraph = NULL;
}
@<Complete passage@> =
current_passage = NULL;
@<Complete paragraph or code@>;
@<Complete paragraph or code@> =
if (current_paragraph) @<Complete paragraph@>
if (current_code) @<Complete code@>
@<Begin paragraph@> =
@<Complete paragraph or code@>;
@<Begin passage@>;
current_paragraph = DocumentationCompiler::new_paragraph(cd->content, NULL);
Trees::make_child(current_paragraph, current_passage);
@<Insert space in paragraph@> =
cdoc_paragraph *P = RETRIEVE_POINTER_cdoc_paragraph(current_paragraph->content);
if (Str::len(P->content) > 0) WRITE_TO(P->content, " ");
@<Insert line in paragraph@> =
cdoc_paragraph *P = RETRIEVE_POINTER_cdoc_paragraph(current_paragraph->content);
WRITE_TO(P->content, "%S", line);
@<Complete paragraph@> =
if (current_paragraph) {
current_paragraph = NULL;
}
@<Begin code@> =
@<Complete paragraph or code@>;
@<Begin passage@>;
current_code = DocumentationCompiler::new_code_sample(cd->content);
Trees::make_child(current_code, current_passage);
pending_code_sample_blanks = 0;
@<Insert line break in code@> =
if (current_code->child) pending_code_sample_blanks++;
@<Insert line in code sample@> =
for (int i=0; i<pending_code_sample_blanks; i++)
Trees::make_child(DocumentationCompiler::new_code_line(cd->content, NULL, 0), current_code);
pending_code_sample_blanks = 0;
Trees::make_child(DocumentationCompiler::new_code_line(cd->content, line, indentation-1), current_code);
@<Complete code@> =
if (current_code) {
current_code = NULL;
}
@
=
void DocumentationCompiler::render(pathname *P, compiled_documentation *cd, text_stream *extras) {
if (cd == NULL) return;
text_stream *OUT = DocumentationCompiler::open_subpage(P, I"index.html");
inform_extension *E = cd->associated_extension;
InformPages::header(OUT, I"Extension", JAVASCRIPT_FOR_ONE_EXTENSION_IRES, NULL);
HTML::incorporate_HTML(OUT, InstalledFiles::filename(EXTENSION_DOCUMENTATION_MODEL_IRES));
@<Write documentation for a specific extension into the page@>;
InformPages::footer(OUT);
DocumentationCompiler::close_subpage();
}
text_stream DOCF_struct;
text_stream *DOCF = NULL;
text_stream *DocumentationCompiler::open_subpage(pathname *P, text_stream *leaf) {
if (P == NULL) return STDOUT;
if (DOCF) internal_error("nested DC writes");
filename *F = Filenames::in(P, leaf);
DOCF = &DOCF_struct;
if (STREAM_OPEN_TO_FILE(DOCF, F, UTF8_ENC) == FALSE)
return NULL; /* if we lack permissions, e.g., then write no documentation */
return DOCF;
}
void DocumentationCompiler::close_subpage(void) {
if (DOCF == NULL) internal_error("no DC page open");
if (DOCF != STDOUT) STREAM_CLOSE(DOCF);
DOCF = NULL;
}
@<Write documentation for a specific extension into the page@> =
if (E) {
inbuild_edition *edition = E->as_copy->edition;
inbuild_work *work = edition->work;
HTML_OPEN("p");
if (Works::is_standard_rules(work) == FALSE)
@<Write Javascript paste icon for source text to include this extension@>;
WRITE("<b>");
Works::write_to_HTML_file(OUT, work, TRUE);
WRITE("</b>");
HTML_CLOSE("p");
HTML_OPEN("p");
HTML::begin_span(OUT, I"smaller");
@<Write up any restrictions on VM usage@>;
@<Write up the version number, if any, and location@>;
HTML::end_span(OUT);
HTML_CLOSE("p");
@<Write up the rubric, if any@>;
}
if (cd->empty) {
HTML_OPEN("p");
WRITE("There is no documentation.");
HTML_CLOSE("p");
} else {
@<Write up the table of contents for the supplied documentation, if any@>;
WRITE("%S", extras);
@<Write up the supplied documentation, if any@>;
}
@<Write Javascript paste icon for source text to include this extension@> =
TEMPORARY_TEXT(inclusion_text)
WRITE_TO(inclusion_text, "Include %X.\n\n\n", work);
PasteButtons::paste_text(OUT, inclusion_text);
DISCARD_TEXT(inclusion_text)
WRITE("&nbsp;");
@<Write up any restrictions on VM usage@> =
compatibility_specification *C = E->as_copy->edition->compatibility;
if (Str::len(C->parsed_from) > 0) {
WRITE("%S", C->parsed_from);
}
@<Write up the version number, if any, and location@> =
semantic_version_number V = E->as_copy->edition->version;
if (VersionNumbers::is_null(V) == FALSE) WRITE("Version %v", &V);
if (E->loaded_from_built_in_area) {
if (VersionNumbers::is_null(V)) { WRITE("Extension"); }
WRITE(" built in to Inform");
}
@<Write up the rubric, if any@> =
if (Str::len(E->rubric_as_lexed) > 0) {
HTML_OPEN("p"); WRITE("%S", E->rubric_as_lexed); HTML_CLOSE("p");
}
if (Str::len(E->extra_credit_as_lexed) > 0) {
HTML_OPEN("p"); WRITE("<i>%S</i>", E->extra_credit_as_lexed); HTML_CLOSE("p");
}
@ This appears above the definition paragraphs because it tends to be only
large extensions which provide TOCs: and they, ipso facto, make many definitions.
If the TOC were directly at the top of the supplied documentation, it might
easily be scrolled down off screen when the user first visits the page.
@<Write up the table of contents for the supplied documentation, if any@> =
Trees::traverse_from(cd->content->root, &DocumentationCompiler::toc, (void *) OUT, 0);
@<Write up the supplied documentation, if any@> =
HTML_OPEN("pre");
WRITE("%S\n", cd->original);
HTML_CLOSE("pre");
@
=
int DocumentationCompiler::toc(tree_node *N, void *state, int L) {
text_stream *OUT = (text_stream *) state;
if (N->type == heading_TNT) {
cdoc_heading *H = RETRIEVE_POINTER_cdoc_heading(N->content);
if (H->level > 0) {
if (H->ID == 1) HTML_TAG("hr"); /* ruled line at top of TOC */
HTML_OPEN("p");
HTML::begin_span(OUT, I"indexblack");
HTML_OPEN("b");
HTML_OPEN_WITH("a", "style=\"text-decoration: none\" href=#docsec%d", H->ID);
if (H->level == 1) WRITE("Chapter %S: ", H->count);
else WRITE("Section %S: ", H->count);
HTML_CLOSE("a");
HTML_CLOSE("b");
HTML::end_span(OUT);
WRITE("%S", H->name);
HTML_CLOSE("p");
}
}
if (N->type == example_TNT) {
cdoc_example *E = RETRIEVE_POINTER_cdoc_example(N->content);
TEMPORARY_TEXT(link)
WRITE_TO(link, "style=\"text-decoration: none\" href=\"eg%d.html#eg%d\"",
E->number, E->number);
HTML::begin_span(OUT, I"indexblack");
HTML_OPEN_WITH("a", "%S", link);
PUT(E->letter); /* the letter A to Z */
WRITE(" &mdash; ");
WRITE("%S", E->name);
HTML_CLOSE("a");
HTML::end_span(OUT);
DISCARD_TEXT(link)
}
return TRUE;
}

View file

@ -252,6 +252,14 @@ far as the user is concerned it opens the example and goes there.
<if-start-of-paragraph> table ...
@h Setting the body text.
Okay, so we can be in any one of these states:
@e WAITING_DSBY from 1
@e PARAGRAPH_DSBY
@e CODE_DSBY
@e QUOTE_DSBY
@
@d EDOC_ALL_EXAMPLES_CLOSED -1 /* do not change this without also changing Extensions */
@d EDOC_FRAGMENT_ONLY -2 /* must differ from this and from all example variant numbers */
@ -262,14 +270,15 @@ int DocumentationRenderer::set_body_text(wording W, OUTPUT_STREAM,
int heading_count = 0, chapter_count = 0, section_count = 0, example_count = 0;
int mid_example = FALSE, skipping_text_of_an_example = FALSE,
start_table_next_line = FALSE, mid_I7_table = FALSE, row_of_table_is_empty = FALSE,
mid_displayed_source_text = FALSE, indentation = 0, close_I6_position = -1;
indentation = 0, close_I6_position = -1;
int dsby_state = WAITING_DSBY;
LOOP_THROUGH_WORDING(i, W) {
int edhl, asterisks;
wording NW = EMPTY_WORDING, RUBW = EMPTY_WORDING;
if (Lexer::word(i) == PARBREAK_V) { /* the lexer records this to mean a paragraph break */
@<Handle a paragraph break@>;
while (Lexer::word(i) == PARBREAK_V) i++;
if (i>Wordings::last_wn(W)) break; /* treat multiple paragraph breaks as one */
@<Enter waiting state@>;
@<Determine indentation of new paragraph@>;
if (indentation == 0 && DocumentationRenderer::extension_documentation_heading(Wordings::from(W, i), &edhl, &NW)) {
heading_count++;
@ -298,15 +307,16 @@ int DocumentationRenderer::set_body_text(wording W, OUTPUT_STREAM,
}
}
if (skipping_text_of_an_example) continue;
@<Handle a line or column break, if there is one@>;
@<Enter paragraph state@>;
@<Transcribe an ordinary word of the documentation@>;
if (close_I6_position == i) WRITE(" -)");
}
@<Enter waiting state@>; // New
if (mid_example) @<Close the previous example's text@>;
if (example_which_is_open != EDOC_FRAGMENT_ONLY) {
@<Handle a paragraph break@>;
}
// if (example_which_is_open != EDOC_FRAGMENT_ONLY) @<Enter waiting state@>;
return example_count;
}
@ -315,17 +325,34 @@ A paragraph break might mean the end of displayed matter (and if so, then also
the end of any table being displayed). Otherwise, it just means a paragraph
break, and a chance to restore our tired variables.
@<Handle a paragraph break@> =
if (mid_displayed_source_text) {
HTML::end_span(OUT);
if (mid_I7_table) @<End I7 table in extension documentation@>;
HTML_CLOSE("blockquote");
} else {
HTML_CLOSE("p");
@<Enter waiting state@> =
switch (dsby_state) {
case WAITING_DSBY: break;
case PARAGRAPH_DSBY: HTML_CLOSE("p");
mid_I7_table = FALSE;
break;
case CODE_DSBY:
HTML::end_span(OUT);
if (mid_I7_table) @<End I7 table in extension documentation@>;
HTML_CLOSE("blockquote");
mid_I7_table = FALSE;
break;
}
dsby_state = WAITING_DSBY;
@<Enter paragraph state@> =
if (dsby_state != PARAGRAPH_DSBY) {
@<Enter waiting state@>;
dsby_state = PARAGRAPH_DSBY;
}
@<Enter code state@> =
if (dsby_state != CODE_DSBY) {
@<Enter waiting state@>;
dsby_state = CODE_DSBY;
HTML_OPEN("blockquote");
HTML::begin_span(OUT, I"indexdullblue");
}
WRITE("\n");
HTML_OPEN("p");
mid_displayed_source_text = FALSE; mid_I7_table = FALSE;
@ The indentation setting is made here because a tab anywhere else does
not mean a paragraph has been indented. Here |i| is at the number of the
@ -406,8 +433,12 @@ take us past the titling line and into the table entries, which we will
need to achieve with an HTML |<table>|.
@<Handle the start of a line which is indented@> =
int j;
if (mid_displayed_source_text) {
int starting_new_code = FALSE;
if (dsby_state != CODE_DSBY) starting_new_code = TRUE;
@<Enter code state@>;
if ((starting_new_code) && (<table-sentence>(Wordings::from(W, i))))
start_table_next_line = TRUE;
if (starting_new_code == FALSE) {
if (start_table_next_line) {
start_table_next_line = FALSE;
mid_I7_table = TRUE;
@ -417,15 +448,9 @@ need to achieve with an HTML |<table>|.
else HTML_TAG("br");
}
if (mid_I7_table) row_of_table_is_empty = TRUE;
} else {
HTML_OPEN("blockquote");
HTML::begin_span(OUT, I"indexdullblue");
mid_displayed_source_text = TRUE;
if (<table-sentence>(Wordings::from(W, i)))
start_table_next_line = TRUE;
}
indentation--;
for (j=0; j<indentation; j++) WRITE("&nbsp;&nbsp;&nbsp;&nbsp;");
for (int j=0; j<indentation; j++) WRITE("&nbsp;&nbsp;&nbsp;&nbsp;");
@h Typesetting the headings.
That is thankfully all for the tormented logic of all those changes of state:

View file

@ -1,142 +0,0 @@
[ExtensionPages::] Individual Pages.
To generate the individual pages on extensions in the extension mini-website.
@ The outer shell function calls the inner one first to generate the main
page of the documentation (where |eg_number| is |-1|), then uses its return
value (the number of examples provided, which may be 0) to generate
associated files for each example.w
=
void ExtensionPages::document_extension(inform_extension *E, int force_update,
inform_project *proj) {
int state = SourceText::for_documentation_only(TRUE);
if (E == NULL) internal_error("no extension");
if (proj == NULL) internal_error("no project");
if (E->documented_on_this_run) return;
if (LinkedLists::len(E->as_copy->errors_reading_source_text) > 0) {
LOG("Not writing documentation on $X because errors occurred scanning it\n",
E->as_copy->edition->work);
} else {
int c, eg_count;
eg_count = ExtensionPages::write_page_inner(E, -1, force_update, proj);
for (c=1; c<=eg_count; c++)
ExtensionPages::write_page_inner(E, c, force_update, proj);
}
E->documented_on_this_run = TRUE;
SourceText::for_documentation_only(state);
}
@
=
int ExtensionPages::write_page_inner(inform_extension *E, int eg_number,
int force_update, inform_project *proj) {
inbuild_edition *edition = E->as_copy->edition;
inbuild_work *work = edition->work;
filename *F = ExtensionWebsite::cut_way_for_page(proj, edition, eg_number);
if (F == NULL) return 0;
int page_exists_already = TextFiles::exists(F);
LOGIF(EXTENSIONS_CENSUS, "Write (%X)/%d %s: %f\n",
work, eg_number, (page_exists_already)?"exists":"does not exist", F);
if (Pathnames::create_in_file_system(Filenames::up(F)) == 0) return 0;
text_stream DOCF_struct;
text_stream *OUT = &DOCF_struct;
if (STREAM_OPEN_TO_FILE(OUT, F, UTF8_ENC) == FALSE)
return 0; /* if we lack permissions, e.g., then write no documentation */
int no_egs = 0;
@<Write the actual extension documentation page@>;
STREAM_CLOSE(OUT);
return no_egs;
}
@<Write the actual extension documentation page@> =
InformPages::header(OUT, I"Extension", JAVASCRIPT_FOR_ONE_EXTENSION_IRES, NULL);
HTML::incorporate_HTML(OUT,
InstalledFiles::filename(EXTENSION_DOCUMENTATION_MODEL_IRES));
@<Write documentation for a specific extension into the page@>;
InformPages::footer(OUT);
@<Write documentation for a specific extension into the page@> =
HTML_OPEN("p");
if (Works::is_standard_rules(work) == FALSE)
@<Write Javascript paste icon for source text to include this extension@>;
WRITE("<b>");
Works::write_to_HTML_file(OUT, work, TRUE);
WRITE("</b>");
HTML_CLOSE("p");
HTML_OPEN("p");
HTML::begin_span(OUT, I"smaller");
@<Write up any restrictions on VM usage@>;
if (E) @<Write up the version number, if any, and location@>;
HTML::end_span(OUT);
HTML_CLOSE("p");
if (E) {
filename *B = ExtensionWebsite::cut_way_for_page(proj, edition, -1);
TEMPORARY_TEXT(leaf)
Filenames::write_unextended_leafname(leaf, B);
@<Write up the rubric, if any@>;
@<Write up the table of contents for the supplied documentation, if any@>;
#ifdef CORE_MODULE
IndexExtensions::document_in_detail(OUT, E);
#endif
@<Write up the supplied documentation, if any@>;
DISCARD_TEXT(leaf)
} else {
HTML_TAG("hr");
}
@<Write Javascript paste icon for source text to include this extension@> =
TEMPORARY_TEXT(inclusion_text)
WRITE_TO(inclusion_text, "Include %X.\n\n\n", work);
PasteButtons::paste_text(OUT, inclusion_text);
DISCARD_TEXT(inclusion_text)
WRITE("&nbsp;");
@<Write up any restrictions on VM usage@> =
compatibility_specification *C = E->as_copy->edition->compatibility;
if (Str::len(C->parsed_from) > 0) {
WRITE("%S", C->parsed_from);
}
@<Write up the version number, if any, and location@> =
semantic_version_number V = E->as_copy->edition->version;
if (VersionNumbers::is_null(V) == FALSE) WRITE("Version %v", &V);
if (E->loaded_from_built_in_area) {
if (VersionNumbers::is_null(V)) { WRITE("Extension"); }
WRITE(" built in to Inform");
}
@<Write up the rubric, if any@> =
if (Str::len(E->rubric_as_lexed) > 0) {
HTML_OPEN("p"); WRITE("%S", E->rubric_as_lexed); HTML_CLOSE("p");
}
if (Str::len(E->extra_credit_as_lexed) > 0) {
HTML_OPEN("p"); WRITE("<i>%S</i>", E->extra_credit_as_lexed); HTML_CLOSE("p");
}
@ This appears above the definition paragraphs because it tends to be only
large extensions which provide TOCs: and they, ipso facto, make many definitions.
If the TOC were directly at the top of the supplied documentation, it might
easily be scrolled down off screen when the user first visits the page.
@<Write up the table of contents for the supplied documentation, if any@> =
wording DW = Extensions::get_documentation_text(E);
if (Wordings::nonempty(DW)) {
HTML_OPEN("p");
DocumentationRenderer::table_of_contents(DW, OUT, leaf);
HTML_CLOSE("p");
}
@<Write up the supplied documentation, if any@> =
wording DW = Extensions::get_documentation_text(E);
if (Wordings::nonempty(DW))
no_egs = DocumentationRenderer::set_body_text(DW, OUT, eg_number, leaf);
else {
HTML_OPEN("p");
WRITE("The extension provides no documentation.");
HTML_CLOSE("p");
}

View file

@ -36,12 +36,12 @@ void ExtensionWebsite::update(inform_project *proj) {
inform_extension *E;
LOOP_OVER_LINKED_LIST(E, inform_extension, proj->extensions_included)
ExtensionPages::document_extension(E, FALSE, proj);
ExtensionWebsite::document_extension(E, FALSE, proj);
linked_list *census = Projects::perform_census(proj);
inbuild_search_result *res;
LOOP_OVER_LINKED_LIST(res, inbuild_search_result, census)
ExtensionPages::document_extension(Extensions::from_copy(res->copy), FALSE, proj);
ExtensionWebsite::document_extension(Extensions::from_copy(res->copy), FALSE, proj);
}
@ The top-level index page is at this filename.
@ -135,3 +135,32 @@ filename *ExtensionWebsite::page_filename_inner(inform_project *proj, inbuild_ed
return F;
}
@ And this is where extension documentation is kicked off.
=
void ExtensionWebsite::document_extension(inform_extension *E, int force_update,
inform_project *proj) {
if (E == NULL) internal_error("no extension");
if (proj == NULL) internal_error("no project");
if (E->documented_on_this_run) return;
inbuild_edition *edition = E->as_copy->edition;
inbuild_work *work = edition->work;
if (LinkedLists::len(E->as_copy->errors_reading_source_text) > 0) {
LOG("Not writing documentation on %X because it has copy errors\n", work);
} else {
compiled_documentation *doc = Extensions::get_documentation(E);
LOG("Writing documentation on %X\n", work);
inbuild_edition *edition = E->as_copy->edition;
filename *F = ExtensionWebsite::cut_way_for_page(proj, edition, -1);
if (F == NULL) return;
pathname *P = Filenames::up(F);
if (Pathnames::create_in_file_system(P) == 0) return;
TEMPORARY_TEXT(details);
#ifdef CORE_MODULE
IndexExtensions::document_in_detail(details, E);
#endif
DocumentationCompiler::render(P, doc, details);
DISCARD_TEXT(details);
}
E->documented_on_this_run = TRUE;
}

View file

@ -279,7 +279,7 @@ void InbuildReport::install(inbuild_copy *C, int confirmed, pathname *to_tool) {
}
@<Make documentation@> =
ExtensionPages::document_extension(Extensions::from_copy(C), FALSE, project);
ExtensionWebsite::document_extension(Extensions::from_copy(C), FALSE, project);
HTML_OPEN("p");
WRITE("Documentation about %S ", C->edition->work->title);
TEMPORARY_TEXT(link)

View file

@ -62,5 +62,6 @@ Chapter 6: Inform Source Text
Chapter 7: Extension Indexing
The Mini-Website
Index Pages
Individual Pages
Documentation Renderer
Documentation Compiler
The Report

View file

@ -43,7 +43,6 @@ int Main::deputy(int argc, char *argv[]) {
inform_project *proj = NULL;
@<Find the project identified for us by Inbuild@>;
@<Open the debugging log and the problems report@>;
@<Name the telemetry@>;
@<Build the project@>;
}
@ -146,29 +145,6 @@ but we won't assume that. Remember, //supervisor// knows best.
HTML::set_link_abbreviation_path(Projects::path(proj));
}
@ Telemetry is not as sinister as it sounds: the app isn't sending data out
on the Internet, only (if requested) logging what it's doing to a local file.
This was provided for classroom use, so that teachers can see what their
students have been getting stuck on. In any case, it needs to be activated
with a use option, so by default this file will never be written.
@<Name the telemetry@> =
pathname *T = Supervisor::transient();
if (T) {
pathname *P = Pathnames::down(T, I"Telemetry");
if (Pathnames::create_in_file_system(P)) {
TEMPORARY_TEXT(leafname_of_telemetry)
int this_month = the_present->tm_mon + 1;
int this_day = the_present->tm_mday;
int this_year = the_present->tm_year + 1900;
WRITE_TO(leafname_of_telemetry,
"Telemetry %04d-%02d-%02d.txt", this_year, this_month, this_day);
filename *F = Filenames::in(P, leafname_of_telemetry);
Telemetry::locate_telemetry_file(F);
DISCARD_TEXT(leafname_of_telemetry)
}
}
@ The compiler is now ready for use. We ask //supervisor// to go ahead and
build that project: it will incrementally build some of the resources needed,
if any of them are, and then call upon //core// to perform the actual

View file

@ -80,7 +80,6 @@ Use predictable randomisation translates as the configuration flag FIX_RNG
in BasicInformKit.
Use numbered rules translates as the configuration flag NUMBERED_RULES
in BasicInformKit.
Use telemetry recordings translates as a compiler feature.
Use no deprecated features translates as the configuration flag NO_DEPRECATED
in BasicInformKit.
Use authorial modesty translates as the configuration flag AUTHORIAL_MODESTY

View file

@ -2,7 +2,7 @@
"is": {
"type": "kit",
"title": "Architecture16Kit",
"version": "10.2.0-beta+6W83"
"version": "10.2.0-beta+6W84"
},
"compatibility": "16-bit",
"kit-details": {

View file

@ -2,7 +2,7 @@
"is": {
"type": "kit",
"title": "Architecture32Kit",
"version": "10.2.0-beta+6W83"
"version": "10.2.0-beta+6W84"
},
"compatibility": "32-bit",
"kit-details": {

View file

@ -2,7 +2,7 @@
"is": {
"type": "kit",
"title": "BasicInformKit",
"version": "10.2.0-beta+6W83"
"version": "10.2.0-beta+6W84"
},
"needs": [ {
"need": {

View file

@ -2,7 +2,7 @@
"is": {
"type": "kit",
"title": "CommandParserKit",
"version": "10.2.0-beta+6W83"
"version": "10.2.0-beta+6W84"
},
"needs": [ {
"need": {

View file

@ -2,7 +2,7 @@
"is": {
"type": "kit",
"title": "EnglishLanguageKit",
"version": "10.2.0-beta+6W83"
"version": "10.2.0-beta+6W84"
},
"needs": [ {
"need": {

View file

@ -2,7 +2,7 @@
"is": {
"type": "kit",
"title": "WorldModelKit",
"version": "10.2.0-beta+6W83"
"version": "10.2.0-beta+6W84"
},
"needs": [ {
"need": {

View file

@ -1,4 +0,0 @@
Xanadu is a room.
* "Gosh, Xanadu is cold for the time of year."

View file

@ -1,9 +0,0 @@
Inform 7 build 6L26 has started.
I've now read your source text, which is 14 words long.
I've also read Standard Rules by Graham Nelson, which is 42597 words long.
I've also read English Language by Graham Nelson, which is 2288 words long.
Problem__ PM_TelemetryAccepted
>--> You wrote '* "Gosh, Xanadu is cold for the time of year."' (source text, line 3):
but that's a message for the Author, not me, so I'll note it down in the
Telemetry file (if you're keeping one.)
Inform 7 has finished: 18 centiseconds used.

View file

@ -251,32 +251,18 @@ void MajorNodes::extra_sentence(parse_node *new) {
}
@ |TRACE_NT| nodes result from asterisked sentences; this is a debugging feature of
Inform. An asterisk on its own toggles logging of work on sentences. An asterisk
followed by double-quoted text is a note for the telemetry file.
Inform. An asterisk on its own toggles logging of work on sentences.
=
@<Pass through a TRACE node@> =
if (Wordings::length(Node::get_text(p)) > 1) {
int tr = telemetry_recording;
telemetry_recording = TRUE;
Telemetry::write_to_telemetry_file(
Lexer::word_text(Wordings::last_wn(Node::get_text(p))));
telemetry_recording = FALSE;
StandardProblems::sentence_problem(Task::syntax_tree(),
_p_(PM_TelemetryAccepted),
"that's a message for the Author, not me",
"so I'll note it down in the Telemetry file (if you're keeping one.)");
telemetry_recording = tr;
} else {
SyntaxTree::toggle_trace(Task::syntax_tree());
text_stream *pass_name = NULL;
switch (global_pass_state.pass) {
case 0: pass_name = I"Pre-Pass"; break;
case 1: pass_name = I"Pass 1"; break;
case 2: pass_name = I"Pass 2"; break;
}
Log::tracing_on(SyntaxTree::is_trace_set(Task::syntax_tree()), pass_name);
SyntaxTree::toggle_trace(Task::syntax_tree());
text_stream *pass_name = NULL;
switch (global_pass_state.pass) {
case 0: pass_name = I"Pre-Pass"; break;
case 1: pass_name = I"Pass 1"; break;
case 2: pass_name = I"Pass 2"; break;
}
Log::tracing_on(SyntaxTree::is_trace_set(Task::syntax_tree()), pass_name);
@

View file

@ -17,7 +17,6 @@ meaningful only for works of IF and are inert for Basic Inform projects.
@e MEMORY_ECONOMY_UO
@e NO_DEPRECATED_FEATURES_UO
@e NUMBERED_RULES_UO
@e TELEMETRY_RECORDING_UO
@e SCORING_UO
@e NO_SCORING_UO
@e ENGINEERING_NOTATION_UO
@ -38,15 +37,14 @@ need to translate this to other languages.
memory economy | ==> { MEMORY_ECONOMY_UO, - }
no deprecated features | ==> { NO_DEPRECATED_FEATURES_UO, - }
numbered rules | ==> { NUMBERED_RULES_UO, - }
telemetry recordings | ==> { TELEMETRY_RECORDING_UO, - }
scoring | ==> { SCORING_UO, - }
no scoring | ==> { NO_SCORING_UO, - }
engineering notation | ==> { ENGINEERING_NOTATION_UO, - }
unabbreviated object names | ==> { UNABBREVIATED_OBJECT_NAMES_UO, - }
no automatic plural synonyms | ==> { NO_AUTO_PLURAL_NAMES_UO, - }
index figure thumbnails | ==> { INDEX_FIGURE_THUMBNAILS_UO, - }
fast route-finding | ==> { FAST_ROUTE_FINDING_UO, - }
slow route-finding | ==> { SLOW_ROUTE_FINDING_UO, - }
fast route-finding | ==> { FAST_ROUTE_FINDING_UO, - }
slow route-finding | ==> { SLOW_ROUTE_FINDING_UO, - }
dictionary resolution ==> { DICTIONARY_RESOLUTION_UO, - }
@ Some of the pragma-like settings are stored here:
@ -113,9 +111,8 @@ void CompilationSettings::set(int U, int N, source_file *from) {
case NO_SCORING_UO: g->scoring_option_set = FALSE; break;
case NUMBERED_RULES_UO: g->number_rules_in_index = TRUE; break;
case SCORING_UO: g->scoring_option_set = TRUE; break;
case TELEMETRY_RECORDING_UO: ProblemBuffer::set_telemetry(); break;
case UNABBREVIATED_OBJECT_NAMES_UO: g->use_exact_parsing_option = TRUE; break;
case NO_AUTO_PLURAL_NAMES_UO: g->no_auto_plural_names = TRUE; break;
case NO_AUTO_PLURAL_NAMES_UO: g->no_auto_plural_names = TRUE; break;
case FAST_ROUTE_FINDING_UO: g->fast_route_finding = TRUE; break;
case SLOW_ROUTE_FINDING_UO: g->slow_route_finding = TRUE; break;
}

View file

@ -122,10 +122,7 @@ might well not be running in the Inform application, but only on the
command line -- deserves the truth.
@<Issue problem summaries for a run without problems@> =
ProblemBuffer::redirect_problem_stream(problems_file);
text_stream *OUT = problems_file;
HTML_OPEN("p");
text_stream *OUT = ProblemBuffer::redirect_problem_stream(problems_file);
if (Problems::warnings_occurred()) {
Problems::issue_problem_begin(Task::syntax_tree(), "**");
Problems::issue_problem_segment(
@ -141,21 +138,9 @@ command line -- deserves the truth.
"brought up to date.");
Problems::issue_problem_end();
UsingProblems::outcome_image_tail(problems_file);
HTML_CLOSE("p");
if (telemetry_recording) {
Telemetry::ensure_telemetry_file();
ProblemBuffer::redirect_problem_stream(telmy);
Problems::issue_problem_begin(Task::syntax_tree(), "**");
Problems::issue_problem_segment(
"The %5-word source text has successfully been translated "
"into a world with %1 %2 and %3 %4, and the index has been "
"brought up to date.");
Problems::issue_problem_end();
WRITE_TO(telmy, "\n");
}
ProblemBuffer::redirect_problem_stream(STDOUT);
WRITE_TO(STDOUT, "\n");
OUT = ProblemBuffer::redirect_problem_stream(STDOUT);
WRITE("\n");
Problems::issue_problem_begin(Task::syntax_tree(), "**");
Problems::issue_problem_segment(
"The %5-word source text has successfully been translated. "
@ -165,9 +150,9 @@ command line -- deserves the truth.
ProblemBuffer::redirect_problem_stream(NULL);
ProgressBar::final_state_of_progress_bar();
text_stream *STATUS = ProgressBar::begin_outcome();
if (STATUS) {
WRITE_TO(STATUS, "Translation succeeded: %d room%s, %d thing%s",
OUT = ProgressBar::begin_outcome();
if (OUT) {
WRITE("Translation succeeded: %d room%s, %d thing%s",
rooms, (rooms==1)?"":"s",
things, (things==1)?"":"s");
ProgressBar::end_outcome();

View file

@ -131,7 +131,6 @@ Use predictable randomisation translates as the configuration flag FIX_RNG
in BasicInformKit.
Use numbered rules translates as the configuration flag NUMBERED_RULES
in BasicInformKit.
Use telemetry recordings translates as a compiler feature.
Use no deprecated features translates as the configuration flag NO_DEPRECATED
in BasicInformKit.
Use authorial modesty translates as the configuration flag AUTHORIAL_MODESTY

View file

@ -16,7 +16,6 @@ Chapter 2: Intranet
Inform Pages
Source Links
Paste Buttons
Documentation Renderer
Installed Files
Documentation References
Localisation

View file

@ -25,16 +25,12 @@ code is general-purpose.
By contrast, the //html// module is very much a part of the Inform compiler
and provides highly Inform-specific functions needed to make the intranet-like
miniature websites it generates on the fly -- problem pages, the Index, the
extensions documentation.
extensions documentation. In particular, it offers:
(a) clickable links to positions in source text, opening the Source panel
in the Inform GUI apps as needed -- see //SourceLinks::link//;
(b) paste buttons which, when clicked, insert text into the Source panel,
or which open certain files or folders -- see //Paste Buttons//;
(c) renders Inform extension documentation text to HTML -- see
//DocumentationRenderer::set_body_text// for the body of an extension or of one
of its examples, and //DocumentationRenderer::table_of_contents// for the TOC at
the top.
@h Custom HTML link protocols.
This seems a useful place to document something which the Inform GUI apps

View file

@ -1,49 +0,0 @@
[Telemetry::] Telemetry.
An optional facility for transcribing the outcome of runs of software.
@ The telemetry file is optional, and transcribes the outcome of each run. This
is mainly for testing Inform, but may also be useful for teachers who want
to monitor how a whole class is using the system.
Nothing is done unless a filename is sent to the following function:
=
filename *spool_telemetry_to = NULL;
void Telemetry::locate_telemetry_file(filename *F) {
spool_telemetry_to = F;
}
@ A little lazily, the telemetry file once opened stays open until the process
finishes.
=
int attempts_to_open_telemetry = 0;
text_stream telemetry_file_struct; /* The actual telemetry file (if created) */
text_stream *telemetry_file = &telemetry_file_struct; /* Main telemetry stream */
text_stream *telmy = NULL; /* Current telemetry stream */
void Telemetry::ensure_telemetry_file(void) {
if (spool_telemetry_to == NULL) return;
if (telmy) return;
if (attempts_to_open_telemetry++ > 0) return;
if (STREAM_OPEN_TO_FILE_APPEND(telemetry_file, spool_telemetry_to, ISO_ENC) == FALSE)
Problems::fatal_on_file("Can't open telemetry file", spool_telemetry_to);
telmy = telemetry_file;
WRITE_TO(telmy, "\n-- -- -- -- -- -- -- --\n%B (build %B): telemetry.\n",
FALSE, TRUE);
int this_month = the_present->tm_mon + 1;
int this_day = the_present->tm_mday;
int this_year = the_present->tm_year + 1900;
WRITE_TO(telmy, "Running on %4d-%02d-%02d at %02d:%02d.\n\n",
this_year, this_month, this_day, the_present->tm_hour, the_present->tm_min);
LOG("Opening telemetry file.\n");
}
@ And log messages can be written here:
=
void Telemetry::write_to_telemetry_file(wchar_t *m) {
Telemetry::ensure_telemetry_file();
WRITE_TO(telmy, "The user says:\n\n%w\n\n", m);
}

View file

@ -99,10 +99,6 @@ text_stream *sigil_of_latest_unlinked_problem = NULL;
@d ACT_ON_SIGIL
LOG("Problem %s issued from %s, line %d\n", sigil, file, line);
if (telemetry_recording) {
Telemetry::ensure_telemetry_file();
WRITE_TO(telmy, "Problem %s issued from %s, line %d\n", sigil, file, line);
}
if (sigil_of_latest_unlinked_problem == NULL)
sigil_of_latest_unlinked_problem = Str::new();
else

View file

@ -147,16 +147,19 @@ void ProblemBuffer::output_problem_buffer_to(OUTPUT_STREAM, int indentation) {
FORMAT_CONSOLE_PROBLEMS_CALLBACK(&sig_mode, &break_width, &fallback);
#endif
if (OUT == problems_file) html_flag = TRUE;
if (sig_mode == FALSE)
for (int k=0; k<indentation; k++) { WRITE(" "); line_width+=2; }
TEMPORARY_TEXT(first)
TEMPORARY_TEXT(second)
TEMPORARY_TEXT(third)
@<Extract details of the first source code reference, if there is one@>;
for (int i=0, L=Str::len(PBUFF); i<L; i++) {
int i = 0;
@<In HTML mode, convert drawing-your-attention arrows@>;
@<In SIG mode, convert drawing-your-attention arrows@>;
for (; i<Str::len(PBUFF); i++) {
int c = Str::get_at(PBUFF, i);
@<In HTML mode, convert drawing-your-attention arrows@>;
@<In SIG mode, convert drawing-your-attention arrows@>;
@<In plain text mode, remove bold and italic HTML tags@>;
if ((html_flag == FALSE) && (c == SOURCE_REF_CHAR))
@<Issue plain text paraphrase of source reference@>
@ -164,6 +167,7 @@ void ProblemBuffer::output_problem_buffer_to(OUTPUT_STREAM, int indentation) {
}
if (html_flag) HTML_CLOSE("p")
else WRITE("\n");
DISCARD_TEXT(first)
DISCARD_TEXT(second)
DISCARD_TEXT(third)
@ -189,6 +193,8 @@ marker,
is converted into a suitable CSS-styled HTML paragraph with hanging
indentation. And similarly for |>++>|, used to mark continuations.
Note that in HTML, this always opens one paragraph tag.
@<In HTML mode, convert drawing-your-attention arrows@> =
if ((html_flag) && (Str::includes_wide_string_at(PBUFF, L">-->", i))) {
if (problem_count > problem_count_at_last_in) {
@ -197,27 +203,23 @@ indentation. And similarly for |>++>|, used to mark continuations.
HTML_OPEN_WITH("p", "class=\"hang\"");
if (currently_issuing_a_warning) WRITE("<b>Warning.</b> ");
else WRITE("<b>Problem.</b> ");
i+=3; continue;
}
if (Str::includes_wide_string_at(PBUFF, L">++>", i)) {
i = 4;
} else if (Str::includes_wide_string_at(PBUFF, L">++>", i)) {
if (html_flag) HTML_OPEN_WITH("p", "class=\"in2\"") else WRITE(" ");
i+=3; continue;
}
if (Str::includes_wide_string_at(PBUFF, L">--->", i)) {
i = 4;
} else if (Str::includes_wide_string_at(PBUFF, L">--->", i)) {
if (html_flag) {
HTML_CLOSE("p"); HTML_TAG("hr");
}
problem_count_at_last_in = problem_count+1;
i+=4; continue;
}
if (Str::includes_wide_string_at(PBUFF, L">+++>", i)) {
i = 5;
} else if (Str::includes_wide_string_at(PBUFF, L">+++>", i)) {
if (html_flag) HTML_OPEN_WITH("p", "halftightin3\"") else WRITE(" ");
i+=4; continue;
}
if (Str::includes_wide_string_at(PBUFF, L">++++>", i)) {
i = 5;
} else if (Str::includes_wide_string_at(PBUFF, L">++++>", i)) {
if (html_flag) HTML_OPEN_WITH("p", "class=\"tightin3\"") else WRITE(" ");
i+=5; continue;
}
i = 6;
} else if (html_flag) HTML_OPEN("p");
@<In SIG mode, convert drawing-your-attention arrows@> =
if ((sig_mode) && (Str::includes_wide_string_at(PBUFF, L">-->", i))) {
@ -230,7 +232,7 @@ indentation. And similarly for |>++>|, used to mark continuations.
WRITE("\033[31m");
WRITE("problem: ");
WRITE("\033[0m");
i+=3; continue;
i = 4;
}
@ The problem messages are put together (by Level 2 below) in a plain text
@ -241,7 +243,7 @@ out when writing to plain text format.
@<In plain text mode, remove bold and italic HTML tags@> =
if (html_flag == FALSE) {
if (c == PROTECTED_LT_CHAR) {
while ((i<L) && (Str::get_at(PBUFF, i) != PROTECTED_GT_CHAR)) i++;
while ((i<Str::len(PBUFF)) && (Str::get_at(PBUFF, i) != PROTECTED_GT_CHAR)) i++;
continue;
}
if ((c == '<') &&
@ -262,7 +264,7 @@ out when writing to plain text format.
(Str::includes_wide_string_at_insensitive(PBUFF, L"</a>", i)) ||
(Str::includes_wide_string_at_insensitive(PBUFF, L"</span>", i)) ||
(Str::includes_wide_string_at_insensitive(PBUFF, L"</font>", i)))) {
while ((i<L) && (Str::get_at(PBUFF, i) != '>')) i++;
while ((i<Str::len(PBUFF)) && (Str::get_at(PBUFF, i) != '>')) i++;
continue;
}
}
@ -330,14 +332,9 @@ our choice.
=
text_stream *redirected_problem_text = NULL; /* Current destination of problem message text */
void ProblemBuffer::redirect_problem_stream(text_stream *S) {
text_stream *ProblemBuffer::redirect_problem_stream(text_stream *S) {
redirected_problem_text = S;
}
int telemetry_recording = FALSE;
void ProblemBuffer::set_telemetry(void) {
telemetry_recording = TRUE;
return S;
}
void ProblemBuffer::output_problem_buffer(int indentation) {
@ -349,12 +346,9 @@ void ProblemBuffer::output_problem_buffer(int indentation) {
WRITE_TO(DL, "\n");
ProblemBuffer::output_problem_buffer_to(DL, indentation);
WRITE_TO(DL, "\n");
if (telemetry_recording) {
WRITE_TO(telmy, "\n");
ProblemBuffer::output_problem_buffer_to(telmy, indentation);
WRITE_TO(telmy, "\n");
}
} else ProblemBuffer::output_problem_buffer_to(redirected_problem_text, indentation);
} else {
ProblemBuffer::output_problem_buffer_to(redirected_problem_text, indentation);
}
}
@h Problems report and index.

View file

@ -10,7 +10,6 @@ Preliminaries
Chapter 1: Starting Up
Problems Module
Telemetry
Chapter 2: Problems
Problems, Level 0

View file

@ -179,14 +179,7 @@ intercepted later on when the problems file is written out as HTML (see
@ 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.
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.

View file

@ -56,6 +56,7 @@ vocabulary_entry *CLOSEBRACE_V = NULL;
vocabulary_entry *CLOSEBRACKET_V = NULL;
vocabulary_entry *COLON_V = NULL;
vocabulary_entry *COMMA_V = NULL;
vocabulary_entry *DOCUMENTATION_V = NULL;
vocabulary_entry *DOUBLEDASH_V = NULL;
vocabulary_entry *FORWARDSLASH_V = NULL;
vocabulary_entry *FULLSTOP_V = NULL;
@ -65,6 +66,7 @@ vocabulary_entry *OPENBRACKET_V = NULL;
vocabulary_entry *OPENI6_V = NULL;
vocabulary_entry *PARBREAK_V = NULL;
vocabulary_entry *PLUS_V = NULL;
vocabulary_entry *QUADRUPLEDASH_V = NULL;
vocabulary_entry *RIGHTARROW_V = NULL;
vocabulary_entry *SEMICOLON_V = NULL;
vocabulary_entry *STROKE_V = NULL;
@ -74,6 +76,7 @@ void Vocabulary::create_punctuation(void) {
CLOSEBRACKET_V = Vocabulary::entry_for_text(L")");
COLON_V = Vocabulary::entry_for_text(L":");
COMMA_V = Vocabulary::entry_for_text(L",");
DOCUMENTATION_V = Vocabulary::entry_for_text(L"documentation");
DOUBLEDASH_V = Vocabulary::entry_for_text(L"--");
FORWARDSLASH_V = Vocabulary::entry_for_text(L"/");
FULLSTOP_V = Vocabulary::entry_for_text(L".");
@ -83,6 +86,7 @@ void Vocabulary::create_punctuation(void) {
OPENI6_V = Vocabulary::entry_for_text(L"(-");
PARBREAK_V = Vocabulary::entry_for_text(PARAGRAPH_BREAK);
PLUS_V = Vocabulary::entry_for_text(L"+");
QUADRUPLEDASH_V = Vocabulary::entry_for_text(L"----");
RIGHTARROW_V = Vocabulary::entry_for_text(L"->");
SEMICOLON_V = Vocabulary::entry_for_text(L";");
STROKE_V = Vocabulary::entry_for_text(L"|");

View file

@ -658,6 +658,20 @@ be a bug, and Inform is bug-free, so it follows that it could never happen.
Lexer::lexer_problem_handler(I6_NEVER_ENDS_LEXERERROR, problem_source_description, NULL);
lxs_kind_of_word = ORDINARY_KW;
@ This slightly crudely (well, very crudely) detects whether the
|---- DOCUMENTATION ----| tear-off marker has been reached in source text.
=
int Lexer::detect_tear_off(void) {
if (lexer_feed_started_at == -1) internal_error("no feed is active");
if (lexer_wordcount < lexer_feed_started_at + 3) return FALSE;
if (lw_array[lexer_wordcount-3].lw_identity != QUADRUPLEDASH_V) return FALSE;
if (lw_array[lexer_wordcount-2].lw_identity != DOCUMENTATION_V) return FALSE;
if (lw_array[lexer_wordcount-1].lw_identity != QUADRUPLEDASH_V) return FALSE;
lexer_wordcount -= 3;
return TRUE;
}
@ The feeder routine is required to send us a triple each time: |cr|
must be a valid character (see above) and may not be |EOF|; |last_cr| must
be the previous one or else perhaps |EOF| at the start of feed;

View file

@ -15,6 +15,7 @@ typedef struct source_file {
int words_of_quoted_text; /* word count for text in double-quotes */
FILE *handle; /* file handle while open */
general_pointer your_ref; /* for the client to attach some meaning */
struct text_stream *torn_off_documentation;
CLASS_DEFINITION
} source_file;
@ -29,6 +30,8 @@ text files, so each of these is converted to a single |'\n'|. Tabs are treated
as if spaces in most contexts, but not when parsing formatted tables, for
instance, so they are not similarly converted.
We also want to look out for the tear-off documentation line, if there is one.
=
source_file *TextFromFiles::feed_open_file_into_lexer(filename *F, FILE *handle,
text_stream *leaf, int documentation_only, general_pointer ref, int mode) {
@ -38,8 +41,9 @@ source_file *TextFromFiles::feed_open_file_into_lexer(filename *F, FILE *handle,
sf->your_ref = ref;
sf->name = F;
sf->handle = handle;
sf->torn_off_documentation = Str::new();
source_location top_of_file;
int cr, last_cr, next_cr, read_cr, newline_char = 0;
int cr, last_cr, next_cr, read_cr, newline_char = 0, torn_off = FALSE;
unicode_file_buffer ufb = TextFiles::create_filtered_ufb(mode);
@ -71,7 +75,12 @@ source_file *TextFromFiles::feed_open_file_into_lexer(filename *F, FILE *handle,
newline_char = 0;
break;
}
Lexer::feed_triplet(last_cr, cr, next_cr);
if (torn_off) {
PUT_TO(sf->torn_off_documentation, cr);
} else {
Lexer::feed_triplet(last_cr, cr, next_cr);
torn_off = Lexer::detect_tear_off();
}
}
sf->text_read = Lexer::feed_ends(TRUE, leaf);
@ -138,6 +147,10 @@ int TextFromFiles::last_lexed_word(source_file *sf) {
return Wordings::last_wn(sf->text_read);
}
text_stream *TextFromFiles::torn_off_documentation(source_file *sf) {
return sf->torn_off_documentation;
}
@ Finally, we translate between the tiresomely many representations of
files we seem to be stuck with. The method used by |TextFromFiles::filename_to_source_file|
looks vulnerable to case-insensitive filename issues, but isn't, because