mirror of
https://github.com/ganelson/inform.git
synced 2024-06-29 05:24:57 +03:00
Made heading tree subordinate to syntax tree
This commit is contained in:
parent
b0e79ec3d5
commit
1005fd668c
|
@ -3,7 +3,8 @@
|
|||
To keep track of the hierarchy of headings and subheadings found
|
||||
in the source text.
|
||||
|
||||
@ Headings in the source text correspond to |HEADING_NT| nodes in syntax
|
||||
@h The hierarchy.
|
||||
Headings in the source text correspond to |HEADING_NT| nodes in syntax
|
||||
trees, and mostly occur when the user has explicitly typed a heading such as:
|
||||
|
||||
>> Part VII - The Ghost of the Aragon
|
||||
|
@ -31,40 +32,6 @@ in a source text is 30 to 100.
|
|||
|
||||
@d NO_HEADING_LEVELS 10
|
||||
|
||||
@ Although it is implicit in the parse tree already, the heading structure
|
||||
is not easy to deduce, and so in this section we build a much smaller tree
|
||||
consisting just of the hierarchy of headings. The heading tree has nodes
|
||||
made from the following structures:
|
||||
|
||||
=
|
||||
typedef struct heading {
|
||||
struct parse_node *sentence_declaring; /* if any: file starts are undeclared */
|
||||
struct source_location start_location; /* first word under this heading is here */
|
||||
int level; /* 0 for Volume (highest) to 5 for Section (lowest) */
|
||||
int indentation; /* in a hierarchical listing */
|
||||
int index_definitions_made_under_this; /* for instance, global variables made here? */
|
||||
int for_release; /* include this material in a release version? */
|
||||
int omit_material; /* if set, simply ignore all of this */
|
||||
int use_with_or_without; /* if TRUE, use with the extension; if FALSE, without */
|
||||
struct inbuild_work *for_use_with; /* e.g. "for use with ... by ..." */
|
||||
struct wording in_place_of_text; /* e.g. "in place of ... in ... by ..." */
|
||||
struct wording heading_text; /* once provisos have been stripped away */
|
||||
struct noun *list_of_contents; /* tagged names defined under this */
|
||||
struct noun *last_in_list_of_contents;
|
||||
struct heading *parent_heading;
|
||||
struct heading *child_heading;
|
||||
struct heading *next_heading;
|
||||
MEMORY_MANAGEMENT
|
||||
} heading;
|
||||
|
||||
@ The headings and subheadings are formed into a tree in which each heading
|
||||
contains its lesser-order headings. The pseudo-heading exists to be the root
|
||||
of this tree; the entire text falls under it. It is not a real heading at all,
|
||||
and has no "level" or "indentation" as such.
|
||||
|
||||
=
|
||||
heading pseudo_heading; /* The entire source falls under this top-level heading */
|
||||
|
||||
@ As an example, a sequence in the primary source text of (Chapter I, Book
|
||||
Two, Section 5, Chapter I, Section 1, Chapter III) would be formed up into
|
||||
the heading tree:
|
||||
|
@ -90,7 +57,7 @@ the same indentation. But when the new heading is lower level than its
|
|||
predecessor (i.e., more important) then the indentation decreases to
|
||||
match the last one equally important.
|
||||
|
||||
We can secure the last of those properties with a formal definition as
|
||||
@ We can secure the last of those properties with a formal definition as
|
||||
follows. The level $\ell_n$ of a heading depends only on its wording (or
|
||||
source file origin), but the indentation of the $n$th heading, $i_n$,
|
||||
depends on $(\ell_1, \ell_2, ..., \ell_n)$, the sequence of all levels so
|
||||
|
@ -116,7 +83,7 @@ $$ i_{m(K)},\qquad {\rm where}\qquad m(K) = {\rm max} \lbrace j \mid 0\leq j < n
|
|||
for each possible heading level $K=0, 1, ..., 9$. This requires much less
|
||||
storage: we call it the "last indentation above level $K$".
|
||||
|
||||
This leads to the following algorithm when looking at the headings in any
|
||||
@ This leads to the following algorithm when looking at the headings in any
|
||||
individual file of source text: at the top of file,
|
||||
= (text as C)
|
||||
for (i=0; i<NO_HEADING_LEVELS; i++) last_indentation_above_level[i] = -1;
|
||||
|
@ -148,6 +115,33 @@ structure currently is, so mistakes are unlikely to last long. This is a
|
|||
classic case where Inform trying to be too clever would annoy more often
|
||||
than assist.
|
||||
|
||||
@h Heading metadata.
|
||||
That's enough theory: now some practice. Each syntax tree also has a
|
||||
heading tree, with one "pseudo-heading" (at notional level $-1$) as root.
|
||||
All nodes are instances of:
|
||||
|
||||
=
|
||||
typedef struct heading {
|
||||
struct parse_node_tree *owning_syntax_tree;
|
||||
struct parse_node *sentence_declaring; /* if any: file starts are undeclared */
|
||||
struct source_location start_location; /* first word under this heading is here */
|
||||
int level; /* 0 for Volume (highest) to 5 for Section (lowest) */
|
||||
int indentation; /* in a hierarchical listing */
|
||||
int index_definitions_made_under_this; /* for instance, global variables made here? */
|
||||
int for_release; /* include this material in a release version? */
|
||||
int omit_material; /* if set, simply ignore all of this */
|
||||
int use_with_or_without; /* if TRUE, use with the extension; if FALSE, without */
|
||||
struct inbuild_work *for_use_with; /* e.g. "for use with ... by ..." */
|
||||
struct wording in_place_of_text; /* e.g. "in place of ... in ... by ..." */
|
||||
struct wording heading_text; /* once provisos have been stripped away */
|
||||
struct noun *list_of_contents; /* tagged names defined under this */
|
||||
struct noun *last_in_list_of_contents;
|
||||
struct heading *parent_heading;
|
||||
struct heading *child_heading;
|
||||
struct heading *next_heading;
|
||||
MEMORY_MANAGEMENT
|
||||
} heading;
|
||||
|
||||
@ =
|
||||
typedef struct name_resolution_data {
|
||||
int heading_count; /* used when tallying up objects under their headings */
|
||||
|
@ -204,7 +198,7 @@ int Headings::new(parse_node_tree *T, parse_node *new, inform_project *proj) {
|
|||
|
||||
heading *Headings::declare(parse_node_tree *T, parse_node *PN) {
|
||||
heading *h = CREATE(heading);
|
||||
|
||||
h->owning_syntax_tree = T;
|
||||
h->parent_heading = NULL; h->child_heading = NULL; h->next_heading = NULL;
|
||||
h->list_of_contents = NULL; h->last_in_list_of_contents = NULL;
|
||||
h->for_release = NOT_APPLICABLE; h->omit_material = FALSE;
|
||||
|
@ -228,7 +222,7 @@ heading *Headings::declare(parse_node_tree *T, parse_node *PN) {
|
|||
@<Determine the indentation from the level@>;
|
||||
|
||||
LOGIF(HEADINGS, "Created heading $H\n", h);
|
||||
if (heading_tree_made_at_least_once) Headings::make_tree();
|
||||
if (heading_tree_made_at_least_once) Headings::make_tree(T);
|
||||
return h;
|
||||
}
|
||||
|
||||
|
@ -372,17 +366,18 @@ to make revisions if late news comes in of a new heading (see above), we
|
|||
begin by removing any existing relationships between the heading nodes.
|
||||
|
||||
=
|
||||
void Headings::make_tree(void) {
|
||||
void Headings::make_tree(parse_node_tree *tree) {
|
||||
heading *h;
|
||||
@<Reduce the whole heading tree to a pile of twigs@>;
|
||||
|
||||
LOOP_OVER(h, heading) {
|
||||
@<If h is outside the tree, make it a child of the pseudo-heading@>;
|
||||
@<Run through subsequent equal or subordinate headings to move them downward@>;
|
||||
}
|
||||
LOOP_OVER(h, heading)
|
||||
if (h->owning_syntax_tree == tree) {
|
||||
@<If h is outside the tree, make it a child of the pseudo-heading@>;
|
||||
@<Run through subsequent equal or subordinate headings to move them downward@>;
|
||||
}
|
||||
|
||||
heading_tree_made_at_least_once = TRUE;
|
||||
Headings::verify_heading_tree();
|
||||
Headings::verify_heading_tree(tree);
|
||||
}
|
||||
|
||||
@ Note that the loop over headings below loops through all those which were
|
||||
|
@ -390,12 +385,14 @@ created by the memory manager: which is to say, all of them except for the
|
|||
pseudo-heading, which was explicitly placed in static memory above.
|
||||
|
||||
@<Reduce the whole heading tree to a pile of twigs@> =
|
||||
tree->heading_root.child_heading = NULL;
|
||||
tree->heading_root.parent_heading = NULL;
|
||||
tree->heading_root.next_heading = NULL;
|
||||
heading *h;
|
||||
pseudo_heading.child_heading = NULL; pseudo_heading.parent_heading = NULL;
|
||||
pseudo_heading.next_heading = NULL;
|
||||
LOOP_OVER(h, heading) {
|
||||
h->parent_heading = NULL; h->child_heading = NULL; h->next_heading = NULL;
|
||||
}
|
||||
LOOP_OVER(h, heading)
|
||||
if (h->owning_syntax_tree == tree) {
|
||||
h->parent_heading = NULL; h->child_heading = NULL; h->next_heading = NULL;
|
||||
}
|
||||
|
||||
@ The idea of the heading loop is that when we place a heading, we also place
|
||||
subsequent headings of lesser or equal status until we cannot do so any longer.
|
||||
|
@ -405,7 +402,7 @@ at the top of the tree.
|
|||
|
||||
@<If h is outside the tree, make it a child of the pseudo-heading@> =
|
||||
if (h->parent_heading == NULL)
|
||||
Headings::make_child_heading(h, &pseudo_heading);
|
||||
Headings::make_child_heading(h, &(tree->heading_root));
|
||||
|
||||
@ Note that the following could be summed up as "move subsequent headings as
|
||||
deep in the tree as we can see they need to be from h's perspective alone".
|
||||
|
@ -488,19 +485,19 @@ our working.
|
|||
|
||||
=
|
||||
int heading_tree_damaged = FALSE;
|
||||
void Headings::verify_heading_tree(void) {
|
||||
Headings::verify_heading_tree_r(&pseudo_heading, -1);
|
||||
void Headings::verify_heading_tree(parse_node_tree *tree) {
|
||||
Headings::verify_heading_tree_r(&(tree->heading_root), &(tree->heading_root), -1);
|
||||
if (heading_tree_damaged) internal_error("heading tree failed to verify");
|
||||
}
|
||||
|
||||
void Headings::verify_heading_tree_r(heading *h, int depth) {
|
||||
void Headings::verify_heading_tree_r(heading *root, heading *h, int depth) {
|
||||
if (h == NULL) return;
|
||||
if ((h != &pseudo_heading) && (depth != h->indentation)) {
|
||||
if ((h != root) && (depth != h->indentation)) {
|
||||
heading_tree_damaged = TRUE;
|
||||
LOG("$H\n*** indentation should be %d ***\n", h, depth);
|
||||
}
|
||||
Headings::verify_heading_tree_r(h->child_heading, depth+1);
|
||||
Headings::verify_heading_tree_r(h->next_heading, depth);
|
||||
Headings::verify_heading_tree_r(root, h->child_heading, depth+1);
|
||||
Headings::verify_heading_tree_r(root, h->next_heading, depth);
|
||||
}
|
||||
|
||||
@h Miscellaneous heading services.
|
||||
|
@ -585,8 +582,9 @@ But when the following is called, we do know that.
|
|||
void Headings::satisfy_dependencies(parse_node_tree *T, inbuild_copy *C) {
|
||||
heading *h;
|
||||
LOOP_OVER(h, heading)
|
||||
if (h->use_with_or_without != NOT_APPLICABLE)
|
||||
Headings::satisfy_individual_heading_dependency(T, C, h);
|
||||
if (h->owning_syntax_tree == T)
|
||||
if (h->use_with_or_without != NOT_APPLICABLE)
|
||||
Headings::satisfy_individual_heading_dependency(T, C, h);
|
||||
}
|
||||
|
||||
@ And now the code to check an individual heading's usage. This whole
|
||||
|
@ -612,21 +610,23 @@ void Headings::satisfy_individual_heading_dependency(parse_node_tree *T, inbuild
|
|||
wchar_t *text = Lexer::word_text(Wordings::first_wn(S));
|
||||
S = Feeds::feed_text(text);
|
||||
}
|
||||
heading *h2; int found = FALSE;
|
||||
if (loaded == FALSE) @<Can't replace heading in an unincluded extension@>
|
||||
else {
|
||||
heading *h2;
|
||||
int found = FALSE;
|
||||
LOOP_OVER(h2, heading)
|
||||
if ((Wordings::nonempty(h2->heading_text)) &&
|
||||
(Wordings::match_perhaps_quoted(S, h2->heading_text)) &&
|
||||
(Works::match(
|
||||
Headings::get_extension_containing(h2)->as_copy->edition->work, work))) {
|
||||
found = TRUE;
|
||||
if (h->level != h2->level)
|
||||
@<Can't replace heading unless level matches@>;
|
||||
Headings::excise_material_under(T, C, h2, NULL);
|
||||
Headings::excise_material_under(T, C, h, h2->sentence_declaring);
|
||||
break;
|
||||
}
|
||||
if (h2->owning_syntax_tree == T)
|
||||
if ((Wordings::nonempty(h2->heading_text)) &&
|
||||
(Wordings::match_perhaps_quoted(S, h2->heading_text)) &&
|
||||
(Works::match(
|
||||
Headings::get_extension_containing(h2)->as_copy->edition->work, work))) {
|
||||
found = TRUE;
|
||||
if (h->level != h2->level)
|
||||
@<Can't replace heading unless level matches@>;
|
||||
Headings::excise_material_under(T, C, h2, NULL);
|
||||
Headings::excise_material_under(T, C, h, h2->sentence_declaring);
|
||||
break;
|
||||
}
|
||||
if (found == FALSE) @<Can't find heading in the given extension@>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ most of these worker functions are in the |core| module, some are not.
|
|||
|
||||
Task::advance_stage_to(SEMANTIC_II_CSEQ, I"Semantic analysis II", -1);
|
||||
BENCH(ParseTreeUsage::verify)
|
||||
BENCH(Headings::make_tree)
|
||||
BENCH(Sentences::Headings::make_the_tree)
|
||||
BENCH(Sentences::Headings::write_as_xml)
|
||||
BENCH(Modules::traverse_to_define)
|
||||
|
||||
|
|
|
@ -45,9 +45,11 @@ void Sentences::Headings::verify_divisions(void) {
|
|||
int total = 0, disaster = FALSE;
|
||||
LOOP_OVER(nt, noun)
|
||||
Sentences::Headings::name_resolution_data(nt)->heading_count = 0;
|
||||
parse_node_tree *T = Task::syntax_tree();
|
||||
LOOP_OVER(h, heading)
|
||||
LOOP_OVER_NOUNS_UNDER(nt, h)
|
||||
Sentences::Headings::name_resolution_data(nt)->heading_count++, total++;
|
||||
if (h->owning_syntax_tree == T)
|
||||
LOOP_OVER_NOUNS_UNDER(nt, h)
|
||||
Sentences::Headings::name_resolution_data(nt)->heading_count++, total++;
|
||||
LOOP_OVER(nt, noun)
|
||||
if (Sentences::Headings::name_resolution_data(nt)->heading_count > 1) {
|
||||
LOG("$z occurs under %d headings\n",
|
||||
|
@ -141,6 +143,20 @@ void Sentences::Headings::disturb(void) {
|
|||
noun_search_list_valid_for_this_heading = NULL;
|
||||
}
|
||||
|
||||
@ The headings and subheadings are formed into a tree in which each heading
|
||||
contains its lesser-order headings. The pseudo-heading exists to be the root
|
||||
of this tree; the entire text falls under it. It is not a real heading at all,
|
||||
and has no "level" or "indentation" as such.
|
||||
|
||||
=
|
||||
void Sentences::Headings::make_the_tree(void) {
|
||||
Headings::make_tree(Task::syntax_tree());
|
||||
}
|
||||
|
||||
heading *Sentences::Headings::pseudo_heading(void) {
|
||||
return &(Task::syntax_tree()->heading_root);
|
||||
}
|
||||
|
||||
@ Leaving aside the cache, then, we build a list as initially empty, then
|
||||
all nametags of priority 1 as found by recursively searching headings, then all
|
||||
nametags of priority 2, and so on.
|
||||
|
@ -182,7 +198,8 @@ ever get that far.
|
|||
@<Start the search list empty@> =
|
||||
nt_search_start = NULL;
|
||||
nt_search_finish = NULL;
|
||||
pseudo_heading.list_of_contents = NULL; /* should always be true, but just in case */
|
||||
heading *pseud = Sentences::Headings::pseudo_heading();
|
||||
pseud->list_of_contents = NULL; /* should always be true, but just in case */
|
||||
|
||||
@ The potential for disaster if this algorithm should be incorrect is high,
|
||||
so we perform a quick count to see if everything made it onto the list
|
||||
|
@ -196,7 +213,7 @@ and produce an internal error if not.
|
|||
LOG("%d tags created, %d in ordering\n", no_tags_attached, c);
|
||||
Sentences::Headings::log_all_headings();
|
||||
LOG("Making fresh tree:\n");
|
||||
Headings::make_tree();
|
||||
Sentences::Headings::make_the_tree();
|
||||
Sentences::Headings::log_all_headings();
|
||||
internal_error_tree_unsafe("reordering of nametags failed");
|
||||
}
|
||||
|
@ -301,7 +318,8 @@ to the index of the project, and to a freestanding XML file.
|
|||
=
|
||||
void Sentences::Headings::log(heading *h) {
|
||||
if (h==NULL) { LOG("<null heading>\n"); return; }
|
||||
if (h==&pseudo_heading) { LOG("<pseudo_heading>\n"); return; }
|
||||
heading *pseud = Sentences::Headings::pseudo_heading();
|
||||
if (h == pseud) { LOG("<pseudo_heading>\n"); return; }
|
||||
LOG("H%d ", h->allocation_id);
|
||||
if (h->start_location.file_of_origin)
|
||||
LOG("<%f, line %d>",
|
||||
|
@ -317,9 +335,12 @@ surreptitiously check that it is correctly formed at the same time.
|
|||
=
|
||||
void Sentences::Headings::log_all_headings(void) {
|
||||
heading *h;
|
||||
LOOP_OVER(h, heading) LOG("$H\n", h);
|
||||
parse_node_tree *T = Task::syntax_tree();
|
||||
LOOP_OVER(h, heading)
|
||||
if (h->owning_syntax_tree == T)
|
||||
LOG("$H\n", h);
|
||||
LOG("\n");
|
||||
Sentences::Headings::log_headings_recursively(&pseudo_heading, 0);
|
||||
Sentences::Headings::log_headings_recursively(Sentences::Headings::pseudo_heading(), 0);
|
||||
}
|
||||
|
||||
void Sentences::Headings::log_headings_recursively(heading *h, int depth) {
|
||||
|
@ -345,7 +366,8 @@ void Sentences::Headings::index(OUTPUT_STREAM) {
|
|||
HTML_OPEN("p");
|
||||
WRITE("CONTENTS");
|
||||
HTML_CLOSE("p");
|
||||
Sentences::Headings::index_heading_recursively(OUT, pseudo_heading.child_heading);
|
||||
Sentences::Headings::index_heading_recursively(OUT,
|
||||
Sentences::Headings::pseudo_heading()->child_heading);
|
||||
contents_entry *ce;
|
||||
int min_positive_level = 10;
|
||||
LOOP_OVER(ce, contents_entry)
|
||||
|
@ -485,13 +507,15 @@ void Sentences::Headings::write_headings_as_xml_inner(OUTPUT_STREAM) {
|
|||
WRITE("<plist version=\"1.0\"><dict>\n");
|
||||
INDENT;
|
||||
WRITE("<key>Application Version</key><string>%B (build %B)</string>\n", FALSE, TRUE);
|
||||
LOOP_OVER(h, heading) {
|
||||
WRITE("<key>%d</key><dict>\n", h->allocation_id);
|
||||
INDENT;
|
||||
@<Write the dictionary of properties for a single heading@>;
|
||||
OUTDENT;
|
||||
WRITE("</dict>\n");
|
||||
}
|
||||
parse_node_tree *T = Task::syntax_tree();
|
||||
LOOP_OVER(h, heading)
|
||||
if (h->owning_syntax_tree == T) {
|
||||
WRITE("<key>%d</key><dict>\n", h->allocation_id);
|
||||
INDENT;
|
||||
@<Write the dictionary of properties for a single heading@>;
|
||||
OUTDENT;
|
||||
WRITE("</dict>\n");
|
||||
}
|
||||
OUTDENT;
|
||||
WRITE("</dict></plist>\n");
|
||||
}
|
||||
|
|
|
@ -33,6 +33,9 @@ typedef struct parse_node_tree {
|
|||
int attachment_sp;
|
||||
struct parse_node *attachment_stack_parent[MAX_ATTACHMENT_STACK_SIZE];
|
||||
struct parse_node *one_off_attachment_point;
|
||||
#ifdef SUPERVISOR_MODULE
|
||||
struct heading heading_root;
|
||||
#endif
|
||||
MEMORY_MANAGEMENT
|
||||
} parse_node_tree;
|
||||
|
||||
|
@ -42,6 +45,11 @@ parse_node_tree *ParseTree::new_tree(void) {
|
|||
T->attachment_sp = 0;
|
||||
T->one_off_attachment_point = NULL;
|
||||
ParseTree::push_attachment_point(T, T->root_node);
|
||||
#ifdef SUPERVISOR_MODULE
|
||||
T->heading_root.parent_heading = NULL;
|
||||
T->heading_root.child_heading = NULL;
|
||||
T->heading_root.next_heading = NULL;
|
||||
#endif
|
||||
return T;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue