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:
parent
fa73c07445
commit
ad2a8e18d8
|
@ -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
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
Prerelease: beta
|
||||
Build Date: 16 July 2023
|
||||
Build Number: 6W83
|
||||
Build Date: 17 July 2023
|
||||
Build Number: 6W84
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
490
inbuild/supervisor-module/Chapter 7/Documentation Compiler.w
Normal file
490
inbuild/supervisor-module/Chapter 7/Documentation Compiler.w
Normal 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(" ");
|
||||
|
||||
@<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(" — ");
|
||||
WRITE("%S", E->name);
|
||||
HTML_CLOSE("a");
|
||||
HTML::end_span(OUT);
|
||||
DISCARD_TEXT(link)
|
||||
}
|
||||
return TRUE;
|
||||
}
|
|
@ -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(" ");
|
||||
for (int j=0; j<indentation; j++) WRITE(" ");
|
||||
|
||||
@h Typesetting the headings.
|
||||
That is thankfully all for the tormented logic of all those changes of state:
|
|
@ -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(" ");
|
||||
|
||||
@<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");
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"is": {
|
||||
"type": "kit",
|
||||
"title": "BasicInformKit",
|
||||
"version": "10.2.0-beta+6W83"
|
||||
"version": "10.2.0-beta+6W84"
|
||||
},
|
||||
"needs": [ {
|
||||
"need": {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"is": {
|
||||
"type": "kit",
|
||||
"title": "CommandParserKit",
|
||||
"version": "10.2.0-beta+6W83"
|
||||
"version": "10.2.0-beta+6W84"
|
||||
},
|
||||
"needs": [ {
|
||||
"need": {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"is": {
|
||||
"type": "kit",
|
||||
"title": "EnglishLanguageKit",
|
||||
"version": "10.2.0-beta+6W83"
|
||||
"version": "10.2.0-beta+6W84"
|
||||
},
|
||||
"needs": [ {
|
||||
"need": {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"is": {
|
||||
"type": "kit",
|
||||
"title": "WorldModelKit",
|
||||
"version": "10.2.0-beta+6W83"
|
||||
"version": "10.2.0-beta+6W84"
|
||||
},
|
||||
"needs": [ {
|
||||
"need": {
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
Xanadu is a room.
|
||||
|
||||
* "Gosh, Xanadu is cold for the time of year."
|
||||
|
|
@ -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.
|
|
@ -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);
|
||||
|
||||
@
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,7 +16,6 @@ Chapter 2: Intranet
|
|||
Inform Pages
|
||||
Source Links
|
||||
Paste Buttons
|
||||
Documentation Renderer
|
||||
Installed Files
|
||||
Documentation References
|
||||
Localisation
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -10,7 +10,6 @@ Preliminaries
|
|||
|
||||
Chapter 1: Starting Up
|
||||
Problems Module
|
||||
Telemetry
|
||||
|
||||
Chapter 2: Problems
|
||||
Problems, Level 0
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"|");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue